--Debug

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

--------------------------------------------------------------------------------
-- API para criação de um novo módulo SGA.
--------------------------------------------------------------------------------
--
-- Esse arquivo possui o "esqueleto" das funções que são necessárias
-- para implementar um novo SGA. A configuração do nó (em sgad-cnf.lua),
-- deverá usar o atributo loadlib para indicar o nome do arquivo que
-- contém o código desenvolvido. Esse atributo faz com que o
-- sga-daemon carregue e execute as funções implementadas. Por exemplo,
-- supondo que o arquivo com a nova API possua o nome NovaAPI.lua, a
-- configuração do nó seria da seguinte forma:
--
-- Node {
--   name = "nomeDaMaquina",
--   platform_id = "SGANovo",
--   loadlib = "NovaAPI",
-- }
--
-- A escolha de configurar o SGA como Node ou como Grid vai depender de
-- como você deseja que este SGA apareça na interface do usuário. Se
-- o usuário deve ver o SGA Boinc como uma única máquina, você deve
-- configurá-lo como Node. Porém, se o Boinc possui vários nós e você
-- deseja que o usuário possa monitorá-los, você deve configurá-lo
-- como Grid.
--
-- Grid {
--   name = "nomeDaMaquina",
--   loadlib = "NovaAPI",
--   default = {
--     platform_id = "SGANovo",
--   },
-- }
--
-- Você encontra mais detalhes em:
-- https://jira.tecgraf.puc-rio.br/confluence/pages/viewpage.action?pageId=37814618
--------------------------------------------------------------------------------

-- @TODO Rever nomenclatura do parametro server na funções

--------------------------------------------------------------------------------
--
-- Especificação das funções públicas
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Incializa o módulo para execução. Essa função deve ser chamada antes
-- de qualquer outra função desta biblioteca.
--
-- Retorna verdadeiro caso o módulo seja inicializado com sucesso. Em caso
-- de erro, retorna nil e a mensagem de erro.
--
-- ok, err = open()
--------------------------------------------------------------------------------
open = function()
  servermanager.localhost = SGAD_CONF.localhost -- XXX
  servermanager.name = SGAD_CONF.name           -- XXX
  local res = os.execute("test -d "..TRASHDIR)
  if  res~= 0 and res~=true and
     not os.execute("mkdir "..TRASHDIR) then
     print("Não foi possível criar o diretório "..TRASHDIR..".")
     print("Usando ./")
     TRASHDIR=""
  end
  return true, nil
end -- function open

--------------------------------------------------------------------------------
-- 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 -- function close

--------------------------------------------------------------------------------
--
-- Funções de consulta à configuração de servidor.
--
-- Em SGAs que são clusters, o parâmetro server indica o nó sobre o
-- qual se deseja obter a informação.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Retorna as informações de configuração correspondentes às chaves e ao
-- servidor especificados.
--
-- @param server o servidor do qual as informações serão obtidas. Caso o valor
--        seja nil será assumido localhost.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões que devem ser suportadas são:
--           csbase_num_processors
--           csbase_memory_ram_info_mb
--           csbase_memory_swap_info_mb
--           csbase_job_control_actions
--           csbase_node_resources
--          @TODO referenciar a IDL
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- map, err = getConfiguration(server, keys)
--------------------------------------------------------------------------------
getConfiguration = function(server, keys)
  print ("Obtendo configuracoes do n� "..server)
  -- @TODO verificar como acessar, via Oil, as constantes definidas na interface IDL
  -- const string SGA_NODE_NUM_PROCESSORS = "csbase_num_processors";
  -- const string SGA_NODE_MEMORY_RAM_INFO_MB = "csbase_memory_ram_info_mb";
  -- const string SGA_NODE_MEMORY_SWAP_INFO_MB = "csbase_memory_swap_info_mb";
  -- @TODO Verificar se essas duas informações são obtidas neste nível ou no daemon
  -- const string SGA_NODE_CLOCK_SPEED_MHZ = "clock_speed_mhz";
  -- const string SGA_NODE_PLATFORM_ID = "csbase_platform_id";
  if not keys or #keys == 0 then
    keys = {
      "csbase_num_processors",
      "csbase_memory_ram_info_mb",
      "csbase_memory_swap_info_mb",
      "csbase_job_control_actions",
      "resources",
    }
  end -- if no keys

  local ninfo = servermanager._nodes[server]
  if not ninfo then
     print(string.format("N� %s n�o encontrado.", server))
     return nil, string.format("N� %s n�o encontrado.", server)
  end

  node = { name = server }
  ninfo = _updatenode(node, maxold)

  local configMap = {}
  for _, k in ipairs(keys) do
    if k == "csbase_num_processors" then
      configMap[k] = servermanager.getnumcpus(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_memory_ram_info_mb" then
      configMap[k] = servermanager.getrammemory(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_memory_swap_info_mb" then
      configMap[k] = servermanager.getswapmemory(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_job_control_actions" then
      configMap[k] = { servermanager.HOLD, servermanager.RELEASE, servermanager.TERMINATE }
    elseif k == "resources" then
      print "Fetching node resources..."
      for k, v in pairs(SGAD_CONF) do
--	print(k,v)
end
	print (SGA_STATIC_CONF.resources)
      if type(SGA_STATIC_CONF.resources) == "table" then
        local nres, totalres = 1, #(SGA_STATIC_CONF.resources)
        while nres <= totalres do
          print("", "resource", SGA_STATIC_CONF.resources[nres])
          configMap[k.."."..nres] = SGA_STATIC_CONF.resources[nres]
          nres = nres + 1
        end
      end
    end 
  end
  for k, v in pairs(configMap) do
      print("configMap - key: ", k, "value: ", v)
  end
  return configMap, nil
end -- function getConfiguration

--------------------------------------------------------------------------------
--
-- Funções de monitoração de servidor.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Retorna as informações dinâmicas correspondentes às chaves e ao servidor
-- especificados.
--
-- @param server o servidor do qual as informações serão obtidas. Caso o valor
--        seja nil será assumido localhost.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
-- @param maxold indica o tempo máximo que a informação pode ter. Caso maxold
--        seja nil, é assumido zero e a informação será buscada.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões são:
--           csbase_load_perc
--           csbase_memory_ram_free
--           csbase_memory_swap_free
--           csbase_number_of_jobs
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- Algunes detalhes sobre as informações padrões:
-- csbase_load_perc:
--   a taxa média de ocupação de CPU do último minuto. 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.
-- csbase_memory_ram_free_perc e csbase_memory_swap_free_perc:
--   a média de ocupação, em bytes, de memória do último minuto.
-- csbase_number_of_jobs:
--   o número de jobs que estão em execução.
--
-- map, err = getInfo(server, keys, maxold)
--------------------------------------------------------------------------------
getInfo = function(server, keys, maxold)
  print("Obtendo informa��es do n� "..server)
  if not keys or #keys == 0 then
    keys = {
      "csbase_load_perc",
      "csbase_memory_ram_free",
      "csbase_memory_swap_free",
      "csbase_number_of_jobs",
    }
  end -- if no keys

  local ninfo = servermanager._nodes[server]
  if not ninfo then
     print(string.format("N� %s n�o encontrado.", server))
     return nil, string.format("N� %s n�o encontrado.", server)
  end

  node = { name = server }
  ninfo = _updatenode(node, maxold)

  local infoMap = {}
  for _, k in ipairs(keys) do
    if k == "csbase_load_perc" then
      infoMap[k] = servermanager.getcpuload(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_memory_ram_free" then
      infoMap[k] = servermanager.getfreerammemory(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_memory_swap_free" then
      infoMap[k] = servermanager.getfreeswapmemory(ninfo) or 0 --servermanager.ERROR_CODE
    elseif k == "csbase_number_of_jobs" then
      infoMap[k] = servermanager.getnumjobs(ninfo) or servermanager.ERROR_CODE
    end
  end

  for k, v in pairs(infoMap) do
      print("infoMap - key: ", k, "value: ", v)
  end

  return infoMap, nil

end -- function getInfo

--------------------------------------------------------------------------------
--
-- Funções de execução, monitoração e controle de processos.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Executa um comando no servidor especificado e retorna um handle que é a
-- referência para o comando no contexto desta biblioteca. O handle pode ter
-- qualquer formato.
-- Os parâmetros extras para a execução podem ser específicos da plataforma ou
-- um dos seguintes já definidos:
--   csbase_command_input_path caminho do direcionamento da entrada padrão.
--   csbase_command_output_path caminho do direcionamento da saída padrão.
--   csbase_command_error_path caminho do direcionamento da saída de erro.
-- Os valores de csbase_command_output_path e csbase_command_error_path podem
-- apontar para o mesmo caminho.
--
-- @param id o identificador único para este comando.
-- @param command o comando a ser executado.
-- @param extraParams os parâmetros extras para a excução do comando.
-- @param server o servidor onde o comando será executado. Caso seja nil será
--        assumido localhost.
--
-- @return o handle do comando e, em caso de erro, nil e a mensagem de erro.
--
-- handle, err = executeCommand(id, command, extraParams, server)
--------------------------------------------------------------------------------
executeCommand = function(id, command, extraParams, server)
  -- const string COMMAND_EXECUTION_INPUT_PATH = "csbase_command_input_path";
  -- const string COMMAND_EXECUTION_OUTPUT_PATH = "csbase_command_output_path";
  -- const string COMMAND_EXECUTION_ERROR_PATH = "csbase_command_error_path";
print("exec")
  for k, v in pairs(extraParams) do
	print("extraParam", k, v)
  end

  local ce_input_p = extraParams.csbase_command_input_path
  local ce_start_p = extraParams.csbase_command_start_path
  local ce_output_p = extraParams.csbase_command_output_path
  local ce_error_p = extraParams.csbase_command_error_path

  local output_dir = ce_output_p or TRASHDIR
  local errorlog = (not ce_error_p) and output_dir.."/"..PBSERRORFILE
  local tmpfile = os.tmpname()

  local cmd = command

  if ce_input_p then
     cmd = cmd .. " < " .. infile
  end

  if ce_start_p 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

    local strextraParams = ""
    if SGAD_CONF and SGAD_CONF.queue_name then

      strextraParams = strextraParams .. " -q " .. SGAD_CONF.queue_name
    end
    local runningJobServer = os.getenv("PBS_O_HOST")
    if runningJobServer then 
        if strextraParams=="" then
              strextraParams = strextraParams .. " -q "
        end
        strextraParams = strextraParams .. "@" .. runningJobServer ..":15001 "
    else
        print ">>>>>> SEM HOST"
    end
  --
  --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
  --
    
--[[
    if host then
      strextraParams = strextraParams .. " -l host=" .. host
    end
]]
    local _outfile = output_dir.."/"..PBSOUTPUTFILE
    local _errorfile = ce_error_p or errorlog
    local exec_string = string.format( [[qsub -N %s -V -o "%s" -e "%s" %s > %s]],
      id, _outfile, _errorfile, strextraParams, tmpfile)
    local workingDir = "./"
    if SGAD_CONF then
       workingDir = SGAD_CONF.path or "./"
    end
    local cmd_string = string.format( [[ksh -c 'cd "%s" && %s']], workingDir, 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 = "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 -- function executeCommand

--------------------------------------------------------------------------------
-- Exerce uma ação sobre um comando iniciado através da função executecommand.
--
-- @param handle o handle do comando.
-- @param action a ação que será feito sobre o comando. Os valores estão
--        definidos na enumeração JobControlAction:
--          SUSPEND: suspender a execução do comando;
--          RESUME: (re)iniciar o comando;
--          HOLD: alterar o estado do comando para on-hold;
--          RELEASE: retirar o comando do estado de on-hold;
--          TERMINATE: terminar o comando.
-- @param childId identificador do job filho no qual será feita a ação, ou nulo
-- se a ação deve ser feita no comando.
--
-- @return true se a ação foi exercida com sucesso e, se cocorrer algum erro,
--         false ou nil e a mensagem de erro.
-- @TODO pensar em como lançar as exceções ActionNotSupportedException, InvalidActionException e InvalidTransitionException
--
-- ok, ex = control(handle, action, childId)
--------------------------------------------------------------------------------
control = function(handle, action, childId)

  if type(handle) ~= "table" or not handle.cmdid then
     return nil, "Handle inválido."
  end

  -- suspend e resume não são suportadas pelo torquePBS
  if action==servermanager.SUSPEND or acton==servermanager.RESUME then
     return nil, "Ação não suportada."
  end

  -- para enviarmos um sinal a um job filho, basta termos o id do filho e o server
  if childId and childId~="" then
     local ok, err = __control(action, childId, handle.server)
     if not ok then
         return false, err
     else
         return true, nil
     end
  end

  -- enviar sinal a todos os filhos
  local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
  local errors = nil
  if err then
     return nil, err
  end
  for i, cmdh in pairs(cmds) do
     local ok, err = __control(action, cmdh.pid, handle.server)
     if not ok then 
        errors = (not errors and err) or errors .. err
     end
  end

  if errors then
     return false, string.format("Ocorreu erro ao realizar ação \"%s\" sobre comando \"%s\" [%s].", action, handle.cmdid, errors)
  end

  return true, nil
end -- function control

__control = function (action, pid, server)
    local errors = nil
    local fname = os.tmpname()
    local command = nil
    local res = nil
    if action==servermanager.TERMINATE then
      command = string.format("qdel %s.%s > %s", pid, server, fname)
      servermanager.writeMsg("Cancelando comando ["..command.."]")
      res = os.execute(command)
    elseif action==servermanager.HOLD then
      command = string.format("qhold %s.%s", cmdh.pid, handle.server)
      servermanager.writeMsg("Colocando hold no comando ["..command.."]")
      res = os.execute(command)
    elseif action==servermanager.RELEASE then
      command = string.format("qrls %s.%s", cmdh.pid, handle.server)
      servermanager.writeMsg("Liberando execução do comando ["..command.."]")
      res = os.execute(command)
    end

    if res~=0 and res ~=true then
      errors = true
      servermanager.writeError(string.format("Falha ao exercer ação \"%s\" sobre comando [%s]", action, handle.cmdid))
    else
      local fd = io.open(fname, "r")
      if not fd then
         local err = "Falha na leitura do arquivo temporário de ação."
         servermanager.writeError(err.." Arq.:["..fname.."]")
      else
         local str = fd:read("*a")
         if str~="" then
            errors = str
         end
      end
      fd:close()
      os.remove(fname)
    end

    if errors then
     return false, string.format("Ocorreu erro ao realizar ação \"%s\" sobre comando \"%s\" [%s].", action, handle.cmdid, errors)
    end

    return true, nil
end

--------------------------------------------------------------------------------
-- Retorna uma tabela Lua com os dados do comando que precisam ser persistidos.
-- Estes dados devem ser suficientes para que um comando seja 'recuperado' após
-- a reinicialização do SGA, através da chamada à retrievecommandhandle.
--
-- @param handle o handle do comando.
--
-- @return os dados do comando ou nil e mensagem de erro na ocorrência 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 -- function getCommandPersistentData

--------------------------------------------------------------------------------
-- A partir dos dados persistidos, retorna um handle que é a referência para o
-- comando no contexto desta biblioteca. O handle pode ter qualquer formato.
--
-- @param pdata os dados do comando.
--
-- @return o handle do comando ou nil e mensagem de erro na ocorrência 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 -- function retrieveCommandHandle

--------------------------------------------------------------------------------
-- 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.

-- @return true se os recursos foram liberados com sucesso e, se cocorrer algum
--         erro, false ou nil e a mensagem de erro.
--
-- ok, err = releaseCommandResources(handle)
--------------------------------------------------------------------------------
releaseCommandResources = function(handle)
   return true, nil
end -- function releaseCommandResources

--------------------------------------------------------------------------------
-- Retorna as informações correspondentes às chaves e ao comando especificados.
--
-- @param handle o handle do comando.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
-- @param maxold indica o tempo máximo que a informação pode ter. Caso maxold
--        seja nil, é assumido zero e a informação será buscada.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões são:
--           csbase_command_pid
--           csbase_command_string
--           csbase_command_exec_host
--           csbase_command_state
--           csbase_command_memory_ram_size_mb
--           csbase_command_memory_swap_size_mb
--           csbase_command_cpu_perc
--           csbase_command_time_sec
--           csbase_command_wall_time_sec
--           csbase_command_user_time_sec
--           csbase_command_system_time_sec
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- Algunes detalhes sobre as informações padrões:
-- csbase_command_id:
--   identificador do comando recebido na função executecommand.
-- csbase_command_pid:
--   identificador do processo iniciado através da função executecommand.
-- csbase_command_ppid:
--   identificador do processo pai iniciado através da função executecommand.
-- csbase_command_string:
--   o comando em execução.
-- csbase_command_exec_host:
--   a máquina que está executando o comando.
-- csbase_command_state:
--   o estado de um processo iniciado através da função executecommand.
-- csbase_command_memory_ram_size_mb:
--   a média de ocupação de memória do último minuto (em MB).
-- csbase_command_memory_swap_size_mb:
--   a média de ocupação da área de swap do último minuto (em MB).
-- csbase_command_cpu_perc:
--   a taxa média de ocupação de CPU do último minuto pelo comando.
-- csbase_command_cpu_time_sec:
--   o tempo de CPU da execução do comando (em SEC).
-- csbase_command_wall_time_sec:
--   o tempo de relógio da execução do comando (em SEC).
-- csbase_command_user_time_sec:
--   o tempo de usuário da execução do comando (em SEC).
-- csbase_command_system_time_sec:
--   o tempo de systema da execução do comando (em SEC).
-- csbase_command_virtual_memory_size_mb:
--   quantidade de memória virtual utilizado na execução do comando (em MB).
-- csbase_command_bytes_in_kb:
--   quantidade de dados lidos na execução do comando (em KB).
-- csbase_command_bytes_out_kb:
--   quantidade de dados escritos na execução do comando (em KB).
-- csbase_command_disk_bytes_read_kb:
--   quantidade de dados lidos do disco na execução do comando (em KB).
-- csbase_command_disk_bytes_write_kb:
--   quantidade de dados escritos no disco na execução do comando (em KB).
--
-- map, err = getCommandInfo(handle, keys, maxold)
--------------------------------------------------------------------------------
getCommandInfo = function(handle, keys, maxold)


  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    servermanager._updatecommands()
    handle.last_update = servermanager.now()
  end

  if not keys or #keys == 0 then
     keys = servermanager.defaultKeys
  end -- if no keys

  local cmdid = servermanager.getcommandid(handle)
  local server = handle.server
  local cmdInfoMap = servermanager.__getCommandInfo(handle, keys, maxold, 0)

  local pid = cmdInfoMap["csbase_command_pid"]

  local fakeHandler = {cmdid = cmdid, server = server, start_time = handle.start_time}
  local children = {}

  if not server or not cmdid then
     print ("Sth really wrong...", "server", server, "cmdid", cmdid)
     os.exit()
  end
  
  local cmds = servermanager._cmds[server] and servermanager._cmds[server][cmdid]
  if not cmds then 
     return cmdInfoMap, nil
  end

  for _, j in ipairs(servermanager._cmds[server][cmdid]) do
     --print ("------------\n possible child job found: " .. j.pid, j.job_state, j.cmdid)
     fakeHandler.pid = j.pid
     fakeHandler.last_update = j.last_update or 0
     local childJobMap = servermanager.__getCommandInfo(fakeHandler, keys, maxold, pid)
     if tonumber(j.pid) ~= handle.pid then
	     table.insert(children, childJobMap)
     end
  end

  for k, v in pairs(cmdInfoMap) do
	print("cmdInfoMap", k, v)
  end

  return cmdInfoMap, children
end

__getCommandInfo = function (handle, keys, maxold, parentId)

  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    servermanager._updatecommands()
    handle.last_update = servermanager.now()
  end

  if not keys or #keys == 0 then
     keys = servermanager.defaultKeys
  end -- if no keys
--[[
  for _, key in ipairs (keys) do
	print("key", key)
  end
]]
  local cmdInfoMap = {}
  for _, k in ipairs(keys) do
    if k == "csbase_command_id" then
      cmdInfoMap[k] = servermanager.getcommandid(handle) or servermanager.ERROR_CODE
    elseif k == "csbase_command_pid" then
      cmdInfoMap[k] = servermanager.getcommandpid(handle) or servermanager.ERROR_CODE
    elseif k == "csbase_command_ppid" then
      cmdInfoMap[k] = parentId
    elseif k == "csbase_command_string" then
      cmdInfoMap[k] = handle.command or servermanager.ERROR_CODE
    elseif k == "csbase_command_exec_host" then
      cmdInfoMap[k] = servermanager.getcommandexechost(handle, maxold) or ""--servermanager.ERROR_CODE
    elseif k == "csbase_command_state" then
      cmdInfoMap[k] = servermanager.getcommandstatus(handle, maxold) or servermanager.ERROR_CODE
    elseif k == "csbase_command_memory_ram_size_mb" then
      local memory = servermanager.getcommandmemoryload(handle, maxold)
      cmdInfoMap[k] = (memory and memory.ram) --or servermanager.ERROR_CODE
    elseif k == "csbase_command_memory_swap_size_mb" then
      local memory = servermanager.getcommandmemoryload(handle, maxold)
      cmdInfoMap[k] = (memory and memory.swap) --or servermanager.ERROR_CODE
    elseif k == "csbase_command_cpu_perc" then
      cmdInfoMap[k] = servermanager.getcommandcpuload(handle, maxold) or servermanager.ERROR_CODE
    elseif k == "csbase_command_cpu_time_sec" then
      cmdInfoMap[k] = servermanager.getcommandcputimesec(handle, maxold) or servermanager.ERROR_CODE
    elseif k == "csbase_command_wall_time_sec" then
      cmdInfoMap[k] = servermanager.getcommandwalltimesec(handle, maxold) or servermanager.ERROR_CODE
    elseif k == "csbase_command_user_time_sec" then
      local timeusage = servermanager.getcommandtimeusage(handle, maxold)
      cmdInfoMap[k] = (timeusage and timeusage.user) or servermanager.ERROR_CODE
    elseif k == "csbase_command_system_time_sec" then
      local timeusage = servermanager.getcommandtimeusage(handle, maxold)
      cmdInfoMap[k] = (timeusage and timeusage.system) or servermanager.ERROR_CODE
    end
  end
--[[
  for k, v in pairs(cmdInfoMap) do
     print ("cmdInfoMap", k, v)
  end
]]
  return cmdInfoMap, nil

end

--------------------------------------------------------------------------------
-- Define as chaves padrões de informações dinâmicas que serão usadas na função
-- getConfiguration, getInfo e getCommandInfo.
-- Caso o usuário não tenha defindo o conjunto de chaves padrões a biblioteca
-- deve ter um conjunto mínimo, que deverá conter as chaves:
--   csbase_load_perc
--   csbase_memory_ram_free_perc
--   csbase_memory_swap_free_perc
--   csbase_number_of_jobs
--
--   csbase_num_processors
--   csbase_memory_ram_info_mb
--   csbase_memory_swap_info_mb
--   csbase_job_control_actions
--   csbase_command_pid
--   csbase_command_string
--   csbase_command_exec_host
--   csbase_command_state
--   csbase_command_memory_ram_size_mb
--   csbase_command_memory_swap_size_mb
--   csbase_command_cpu_perc
--   csbase_command_time_sec
--   csbase_command_wall_time_sec
--   csbase_command_user_time_sec
--   csbase_command_system_time_sec
-- @param keys o conjunto de chaves de configuração
--------------------------------------------------------------------------------
setDefaultKeys = function(keys)
  if not keys or #keys==0 then
	return
  end
  defaultKeys = {}
  for _,v in ipairs(keys) do
	table.insert(defaultKeys, v)
  end
  servermanager.defaultKeys = defaultKeys
end -- function setDefaultKeys

--------------------------------------------------------------------------------
-- Retorna todas as chaves disponíveis.
--------------------------------------------------------------------------------
getAllKeys = function()
  defaultKeysCopy = {}
  for _,v in ipairs(servermanager.defaultKeys) do
	table.insert(defaultKeysCopy, v)
  end
  return defaultKeysCopy
end -- function getAllKeys

--------------------------------------------------------------------------------
--
-- Funções de consulta à clusters.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Obtém as informações de todos os processos em execução no SGA.
-- ** Em desenvolvimento **
--------------------------------------------------------------------------------
getJobsInfo = function()
  local tmpname = os.tmpname()
  local command = string.format('qstat -x > %s', tmpname)

  servermanager.writeMsg("Coletando info sobre jobs com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 and result~=true then
     local err = "Falha na coleta de dados de jobs (qstat)."
     servermanager.writeError(err.." Cod.:"..tostring(result))
     return
  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."
     servermanager.writeError(err.." Arq.:["..tmpname.."]")
     return
  end

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

  return strXML
end

--------------------------------------------------------------------------------
-- Retorna uma lista com os nós do servidor especificado.
--
-- @param server o servidor. Caso o valor seja nil, assume localhost.
--
-- @return a lista de nós e ,em caso de erro, nil e a mensagem de erro.
--
-- nodes, err = getnodes(server)
--------------------------------------------------------------------------------
getNodes = function()
   print "GETNODES"
--
-- PROBLEMA: OVO/GALINHA! :-)
--
-- ninfo = servermanager._getnode(server)
-- if not ninfo then
--    return nil, servermanager.NO_SERVER
-- end
   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)

   --print(str)

   for host in string.gmatch("\n"..str, "\n(%S+)\n") do
     local hostname = string.gsub(host,"%..*","")
     print("found node", hostname)
     table.insert(ids, hostname)
   end

  if #ids > 0 then
     for _, id in ipairs(ids) do
        servermanager._nodes[id] = { name = id }
     end
     return ids, nil
  else
     return nil, str
  end
end

--------------------------------------------------------------------------------
--
-- Funções de log de histórico
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Indica se o histórico deve ser ativado ou não para o SGA em questão.
--
-- setHistoric(historic)
--------------------------------------------------------------------------------
setHistoric = function(historic)
  servermanager._enableHistoric = historic
end

--------------------------------------------------------------------------------
-- Retorna true se o histórico deve ser ativado para o sga em questão,
-- ou false caso contrário.
--
-- enableHistoric = getHistoric()
--------------------------------------------------------------------------------
getHistoric = function()
  return servermanager._enableHistoric
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( "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
 
_updatecommands = function( )
  local cmds = {}
  local fname = os.tmpname()
  local command = string.format('qstat -f > %s', fname)

  servermanager.writeMsg("Coletando info com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 and result~=true then
     print("resultado da coleta", result)
     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, maxold)

  if maxold and type(maxold)==number and servermanager.now() - servermanager._nodes[node.name]._lastUpdate < maxold then
    return servermanager._nodes[node.name]
  end

  local attr_table = {}

  local fname = os.tmpname()
  local domain = node.domain or SGAD_CONF.domain or ""
  local command = string.format('pbsnodes -a %s%s > %s', node.name, domain, fname)

  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)

  for attr, val in string.gmatch(str, "(%w+) = ([^\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
      if attr=="totmem" or attr=="physmem" then
      end
      attr_table[attr] = attr_table[attr] or val
    end
  end
  if attr_table.jobs then
    local jobs = attr_table.jobs
    attr_table.jobs = {}
    for job in string.gmatch(jobs, "([^,| ]+)") do
      if job == "?" then
         break
      end
      print ("job", job)
      table.insert(attr_table.jobs, job)
    end
  end
  attr_table._lastUpdate = servermanager.now()
  servermanager._nodes[node.name] = attr_table
  return attr_table
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._memvaluetomegabytes(val, unit)

  return val
end

_memvaluetomegabytes = function (value,unit)
  unit = string.upper(unit)
  if unit == "T" then
    return value * (1024^2)
  elseif unit == "G" then
    return value * 1024
  elseif unit == "M" then
    return value
  elseif unit == "K" or unit=="k" then
    return value * (1024^-1)
  else
    return value
  end
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

------------------------------------------------------------------------
-- 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
     local server = cmdh["exec_host"] or handle.server
     local state
     local job_state = cmdh.job_state
     if job_state then
       if job_state == 'E' or
          job_state == 'C' or
          job_state == 'D' or
          job_state == 'A' then
          state = servermanager.FINISHED
          servermanager._checkforerror(handle)
       elseif job_state == 'Q' then
          state = servermanager.WAITING
       else
          state = servermanager.RUNNING
       end
     else
        state = servermanager.NOT_RESPONDING
     end

     if walltime ~= 0 and walltime~=servermanager.ERROR_CODE and state==servermanager.RUNNING then
       print("walltime", walltime)
       local node = {}
       node.name = handle.host
       local ninfo = servermanager._updatenode(node)
       local ncpus, err = servermanager.getnumcpus(ninfo)
       ncpus = ncpus or 1

       cpuperc = (cput / ncpus) /walltime
     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

getnumcpus = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return tonumber(ninfo.ncpus), nil
end

getrammemory = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   if ninfo.physmem then
      return servermanager._getvalunit(ninfo.physmem), nil
   end
   return nil, nil
end

gettotalmemory = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   if ninfo.totmem then
      return servermanager._getvalunit(ninfo.totmem), nil
   end
   return nil, nil
end

getswapmemory = function (ninfo)
   local tmem = servermanager.gettotalmemory(ninfo)
   local ram = servermanager.getrammemory(ninfo)
   if tmem and ram and type(tmem)=="number" and type(ram)=="number" then
      return tmem-ram
   end
   return nil, servermanager.NO_SERVER
end

getnumjobs = function (ninfo)
   if not ninfo or not ninfo.jobs or type(ninfo.jobs)~="table" then
      return nil, servermanager.NO_SERVER
   end
   return #(ninfo.jobs), nil
end

getfreerammemory = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   local availmem = false
   if ninfo.availmem then
      availmem = servermanager._getvalunit(ninfo.availmem) 
   end
   local rammem = servermanager.getrammemory(ninfo)
   local totalmem = servermanager.gettotalmemory(ninfo)
   print("availmem", availmem)
   
   if availmem then
      local mload = totalmem - availmem
      if mload <= rammem then 
         return rammem - mload
      else
         return 0
      end
   end
   return nil, servermanager.NO_SERVER
end

getfreeswapmemory = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   if ninfo.availmem then
      local availmem = servermanager._getvalunit(ninfo.availmem) 
   end
   local rammem = servermanager.getrammemory(ninfo)
   local totalmem = servermanager.gettotalmemory(ninfo)
   local swapmem = servermanager.getswapmemory(ninfo)
   if availmem then
      local mload = totalmem - availmem
      if mload <= rammem then 
         return swapmem
      else
         return swapmem-(mload-rammem)
      end
   end
   return nil, servermanager.NO_SERVER
end

getcpuload = function (ninfo)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return tonumber(ninfo.loadave), 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 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.
--
-- 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 */
--
-- 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 state == 'E' or
        state == 'C' or
        state == 'D' or
        state == 'A' then
        status = servermanager.FINISHED
        servermanager._checkforerror(handle)
     elseif state == 'Q' or
	    state == 'H' 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 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


------------------------------------------------------------------------
-- 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

getcommandwalltimesec = function (handle, maxold)
   local procs, err = servermanager.getcommandallinfo(handle, maxold)
   if not procs then
      return 0
   end
   return tonumber(procs[1].wallTimeSec)
end

getcommandcputimesec = function (handle, maxold)
   local procs, err = servermanager.getcommandallinfo(handle, maxold)
   if not procs then
      return 0
   end
   return tonumber(procs[1].CPUTimeSec)
end



--------------------------------------------------------------------------------

local defaultKeys = {
   "csbase_load_perc",
   "csbase_memory_ram_free_perc",
   "csbase_memory_swap_free_perc",
   "csbase_number_of_jobs",

   "csbase_num_processors",
   "csbase_memory_ram_info_mb",
   "csbase_memory_swap_info_mb",
   "csbase_job_control_actions",
   "csbase_command_pid",
   "csbase_command_string",
   "csbase_command_exec_host",
   "csbase_command_state",
   "csbase_command_memory_ram_size_mb",
   "csbase_command_memory_swap_size_mb",
   "csbase_command_cpu_perc",
   "csbase_command_time_sec",
   "csbase_command_wall_time_sec",
   "csbase_command_user_time_sec",
   "csbase_command_system_time_sec",
}

servermanager = {
  -------------
  -- private --
  -------------
  _enableHistoric = false,

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

  ERROR_CODE = -1,

  -----------------------
  -- ACTIONS
  -----------------------
  SUSPEND = "SUSPEND",
  RESUME = "RESUME",
  HOLD = "HOLD",
  RELEASE = "RELEASE",
  TERMINATE = "TERMINATE",

  -------------
  -- public --
  -------------
  now = now, -- Definida em C nos binários para Unix.
  sleep = sleep, -- Definida em C nos binários para Unix.

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

  -- Funções de consulta à configuração de servidor:
  getConfiguration = getConfiguration,

  -- Funções de monitoração de servidor:
  getInfo = getInfo,

  -- Funções de execução, monitoração e controle de processos:
  executeCommand = executeCommand,
  control = control,
  getCommandPersistentData = getCommandPersistentData,
  retrieveCommandHandle = retrieveCommandHandle,
  releaseCommandResources = releaseCommandResources,
  getCommandInfo = getCommandInfo,

  -- Funções para clusters:
  getJobsInfo = getJobsInfo,
  getNodes = getNodes, -- OPCIONAL

  -- Funções de log de histórico:
  setHistoric = setHistoric, -- OPCIONAL
  getHistoric = getHistoric, -- OPCIONAL

  -- API antiga
  NO_SERVER= "Servidor não existente", -- Colocar num arquivo de mensagens?
  _nodes   = {},
  _cmds    = {},
  _commandsdump = _commandsdump,
  _getcommand = _getcommand,
  _getcommandall = _getcommandall,
  _getvalunit = _getvalunit,
  _memvaluetobytes = _memvaluetobytes,
  _memvaluetomegabytes = _memvaluetomegabytes,
  _str2sec = _str2sec,
  _updatecommand = _updatecommand,
  _updatecommands = _updatecommands,
  _updatenode = _updatenode,
  _checkforerror = _checkforerror,
  __getCommandInfo = __getCommandInfo,
  getnumcpus = getnumcpus,
  getnumjobs = getnumjobs,
  getrammemory = getrammemory,
  getswapmemory = getswapmemory,
  getfreerammemory = getfreerammemory,
  getfreeswapmemory = getfreeswapmemory,
  gettotalmemory = gettotalmemory,
  getcpuload = getcpuload,
  getmemoryload = getmemoryload,
  getnumberofjobs = getnumberofjobs,

  writeMsg = writeMsg,
  writeError = writeError,
  writeCmd = writeCmd,

  -- Funções de execução, monitoração e controle de processos:
  getcommandallinfo = getcommandallinfo,               -- EXTRA
  getcommandid = getcommandid,
  getcommandpid = getcommandpid,
  getcommandstatus = getcommandstatus,
  getcommandexechost = getcommandexechost,
  getcommandcpuload = getcommandcpuload,
  getcommandmemoryload = getcommandmemoryload,
  getcommandtimeusage = getcommandtimeusage,
  getcommandwalltimesec = getcommandwalltimesec,
  getcommandcputimesec = getcommandcputimesec,

  defaultKeys = defaultKeys,
  
}
