// @(#)root/proofplayer:$Id$
// Author: G.Ganis July 2011

/*************************************************************************
 * Copyright (C) 1995-2004, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/


/** \class TProofMonSenderSQL
\ingroup proofkernel

TProofMonSender implementation for the SQL writers

*/

#include "TProofMonSenderSQL.h"

#include "TDSet.h"
#include "TFileInfo.h"
#include "THashList.h"
#include "TList.h"
#include "TPluginManager.h"
#include "TProofDebug.h"
#include "TROOT.h"
#include "TSystem.h"
#include "TObjString.h"
#include "TVirtualMonitoring.h"

////////////////////////////////////////////////////////////////////////////////
/// Main constructor

TProofMonSenderSQL::TProofMonSenderSQL(const char *serv, const char *user,
                                       const char *pass, const char *table,
                                       const char *dstab, const char *filestab)
                  : TProofMonSender(serv,"ProofMonSenderSQL"),
                    fDSetSendOpts("bulk,table=proofquerydsets"),
                    fFilesSendOpts("bulk,table=proofqueryfiles")
{
   fWriter = 0;
   // Init the sender instance using the plugin manager
   TPluginHandler *h = 0;
   if ((h = gROOT->GetPluginManager()->FindHandler("TVirtualMonitoringWriter", "SQL"))) {
      if (h->LoadPlugin() != -1) {
         fWriter = (TVirtualMonitoringWriter *) h->ExecPlugin(4, serv, user, pass, table);
         if (fWriter && fWriter->IsZombie()) SafeDelete(fWriter);
      }
   }
   // Flag this instance as valid if the writer initialization succeeded
   if (fWriter) ResetBit(TObject::kInvalidObject);

   // Set default send control options
   SetBit(TProofMonSender::kSendSummary);
   SetBit(TProofMonSender::kSendDataSetInfo);
   SetBit(TProofMonSender::kSendFileInfo);
   fSummaryVrs = 2;
   fDataSetInfoVrs = 1;
   fFileInfoVrs = 1;

   // Transfer verbosity requirements
   PDB(kMonitoring,1) if (fWriter) fWriter->Verbose(kTRUE);

   // Reformat the send options strings, if needed
   if (dstab && strlen(dstab) > 0) fDSetSendOpts.Form("bulk,table=%s", dstab);
   if (filestab && strlen(filestab) > 0) fFilesSendOpts.Form("bulk,table=%s", filestab);
}

////////////////////////////////////////////////////////////////////////////////
/// Destructor

TProofMonSenderSQL::~TProofMonSenderSQL()
{
   SafeDelete(fWriter);
}

////////////////////////////////////////////////////////////////////////////////
/// Send 'summary' record for the table 'proofquerylog'.
///
/// There are three versions of this record, corresponding the evolution
/// in time of the monitoring requirements.
///
/// The default version 2 corresponds to the table created with the following command:
///
/// CREATE TABLE proofquerylog (
///    id int(11) NOT NULL auto_increment,
///    proofuser varchar(32) NOT NULL,
///    proofgroup varchar(32) default NULL,
///    querybegin datetime default NULL,
///    queryend datetime default NULL,
///    walltime int(11) default NULL,
///    cputime float default NULL,
///    bytesread bigint(20) default NULL,
///    events bigint(20) default NULL,
///    totevents bigint(20) default NULL,
///    workers int(11) default NULL,
///    querytag varchar(64) NOT NULL,
///    vmemmxw bigint(20) default NULL,
///    rmemmxw bigint(20) default NULL,
///    vmemmxm bigint(20) default NULL,
///    rmemmxm bigint(20) default NULL,
///    numfiles int(11) default NULL,
///    missfiles int(11) default NULL,
///    status int(11) default NULL,
///    rootver varchar(32) NOT NULL,
///    PRIMARY KEY (id) );
///
/// Version 1 corresponds to the table created with the following command:
///    ('user','begin','end' instead of 'proofuser', 'querybegin', 'queryend';
///     no 'status', 'missfiles', 'rootver'; 'dataset' field with name(s) of
///     processed dataset(s))
///
/// CREATE TABLE proofquerylog (
///    id int(11) NOT NULL auto_increment,
///    user varchar(32) NOT NULL,
///    proofgroup varchar(32) default NULL,
///    begin datetime default NULL,
///    end datetime default NULL,
///    walltime int(11) default NULL,
///    cputime float default NULL,
///    bytesread bigint(20) default NULL,
///    events bigint(20) default NULL,
///    totevents bigint(20) default NULL,
///    workers int(11) default NULL,
///    querytag varchar(64) NOT NULL,
///    vmemmxw bigint(20) default NULL,
///    rmemmxw bigint(20) default NULL,
///    vmemmxm bigint(20) default NULL,
///    rmemmxm bigint(20) default NULL,
///    numfiles int(11) default NULL,
///    dataset varchar(512) NOT NULL,
///    PRIMARY KEY (id) );

Int_t TProofMonSenderSQL::SendSummary(TList *recs, const char *dumid)
{
   //
   // Version 0 corresponds to the table created with the following command:
   //    ('group' instead of 'proofgroup'; no 'querytag', 'vmemmxw',
   //     'rmemmxw', 'vmemmxm', 'rmemmxm', 'numfiles', 'dataset')
   //
   // CREATE TABLE proofquerylog (
   //    id int(11) NOT NULL auto_increment,
   //    user varchar(32) NOT NULL,
   //    group varchar(32) default NULL,
   //    begin datetime default NULL,
   //    end datetime default NULL,
   //    walltime int(11) default NULL,
   //    cputime float default NULL,
   //    bytesread bigint(20) default NULL,
   //    events bigint(20) default NULL,
   //    totevents bigint(20) default NULL,
   //    workers int(11) default NULL,
   //    PRIMARY KEY (id) );
   //
   //  Return 0 on success, -1 on any failure.

   if (!IsValid()) {
      Error("SendSummary", "invalid instance: do nothing!");
      return -1;
   }

   // Are we requested to send this info?
   if (!TestBit(TProofMonSender::kSendSummary)) return 0;

   PDB(kMonitoring,1) Info("SendSummary", "preparing (qid: '%s')", dumid);

   // Make sure we have something to send
   if (!recs || (recs && recs->GetSize() <= 0)) {
      Error("SendSummary", "records list undefined or empty!");
      return -1;
   }
   TList *xrecs = recs;

   TObject *dsn = 0;
   TNamed *nm = 0;
   // We may need to correct some variable names first
   if (fSummaryVrs > 1) {
      if ((nm = (TNamed *) recs->FindObject("user"))) nm->SetName("proofuser");
      if ((nm = (TNamed *) recs->FindObject("begin"))) nm->SetName("querybegin");
      if ((nm = (TNamed *) recs->FindObject("end"))) nm->SetName("queryend");
      if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn);
   } else if (fSummaryVrs == 0) {
      // Only the first records
      xrecs = new TList;
      xrecs->SetOwner(kFALSE);
      TIter nxr(recs);
      TObject *o = 0;
      while ((o = nxr())) {
         if (!strcmp(o->GetName(), "vmemmxw")) break;
         xrecs->Add(o);
      }
   }

   PDB(kMonitoring,1) Info("SendSummary", "sending (%d entries)", xrecs->GetSize());

   // Now we are ready to send
   Bool_t rc = fWriter->SendParameters(xrecs, dumid);

   // Restore the "dataset" entry in the list
   if (fSummaryVrs > 1 && dsn && xrecs == recs) {
      TObject *num = recs->FindObject("numfiles");
      if (num)
         recs->AddBefore(num, dsn);
      else
         recs->Add(dsn);
   }
   if (xrecs != recs) SafeDelete(xrecs);

   // Done
   return (rc ? 0 : -1);
}

////////////////////////////////////////////////////////////////////////////////
/// Post information about the processed dataset(s). The information is taken
/// from the TDSet object 'dset' and integrated with the missing files
/// information in the list 'missing'. The string 'qid' is the uninque
/// ID of the query; 'begin' the starting time.
///
/// The record is formatted for the table 'proofquerydsets'.
///
/// There are two versions of this record, with or without the starting time.
/// The starting time could be looked up from the summary record, if available.
///
/// The default version 1 corresponds to the table created with the following command:
///
/// CREATE TABLE proofquerydsets (
///    id int(11) NOT NULL auto_increment,
///    dsn varchar(512) NOT NULL,
///    querytag varchar(64) NOT NULL,
///    querybegin datetime default NULL,
///    numfiles int(11) default NULL,
///    missfiles int(11) default NULL,
///    PRIMARY KEY  (id),
///    KEY ix_querytag (querytag) );
///
/// Version 0 corresponds to the table created with the following command:
///    (no 'querybegin')
///
/// CREATE TABLE proofquerydsets (
///    id int(11) NOT NULL auto_increment,
///    dsn varchar(512) NOT NULL,
///    querytag varchar(64) NOT NULL,
///    numfiles int(11) default NULL,
///    missfiles int(11) default NULL,
///    PRIMARY KEY  (id),
///    KEY ix_querytag (querytag) );
///
/// The information is posted with a bulk insert.
///
/// Returns 0 on success, -1 on failure.

Int_t TProofMonSenderSQL::SendDataSetInfo(TDSet *dset, TList *missing,
                                          const char *begin, const char *qid)
{
   if (!IsValid()) {
      Error("SendDataSetInfo", "invalid instance: do nothing!");
      return -1;
   }

   // Are we requested to send this info?
   if (!TestBit(TProofMonSender::kSendDataSetInfo)) return 0;

   // The query id (tag) must be given
   if (!qid || (qid && strlen(qid) <= 0)) {
      Error("SendDataSetInfo", "query id (tag) undefined!");
      return -1;
   }
   // The dataset must be given
   if (!dset) {
      Error("SendDataSetInfo", "TDSet object undefined! (qid: '%s')", qid);
      return -1;
   }

   PDB(kMonitoring,1) Info("SendDataSetInfo", "preparing (qid: '%s')", qid);

   TList plets;
   // Extract the information and save it into the relevant multiplets
   TString dss(dset->GetName()), ds;
   Ssiz_t from = 0;
   while ((dss.Tokenize(ds, from , "[,| ]"))) {
      // Create a new TDSetPlet and add it to the list
      plets.Add(new TDSetPlet(ds.Data(), dset));
   }

   // Now try to count the files
   TDSetPlet *plet = 0;
   TIter nxpl(&plets);
   TObject *o = 0;
   TDSetElement *e = 0, *ee = 0;
   TDSet *dsete = 0;
   TIter nxe(dset->GetListOfElements());
   TString dse;
   while ((o = nxe())) {
      if ((e = dynamic_cast<TDSetElement *>(o))) {
         dse = e->GetDataSet();
         if (!dse.IsNull()) {
            nxpl.Reset();
            while ((plet = (TDSetPlet *) nxpl())) {
               if (dse == plet->GetName()) {
                  plet->fFiles += 1;
                  break;
               }
            }
         }
      } else if ((dsete = dynamic_cast<TDSet *>(o))) {
         PDB(kMonitoring,1)
            Info("SendDataSetInfo", "dset '%s' (%d files)",
                                    o->GetName(), dsete->GetListOfElements()->GetSize());
         TIter nxee(dsete->GetListOfElements());
         while ((ee = (TDSetElement *) nxee())) {
            dse = ee->GetDataSet();
            if (!dse.IsNull()) {
               nxpl.Reset();
               while ((plet = (TDSetPlet *) nxpl())) {
                  if (dse == plet->GetName()) {
                     plet->fFiles += 1;
                     plet->fDSet = dsete;
                     break;
                  }
               }
            }
         }
      } else {
         Warning("SendDataSetInfo", "ignoring unknown element type: '%s'", o->ClassName());
      }
   }

   // Now try to include the missing files info
   if (missing) {
      TFileInfo *fi = 0;
      TIter nxm(missing);
      TString dsfi, fn;
      while ((fi = (TFileInfo *) nxm())) {
         dsfi = fi->GetTitle();
         if (!dsfi.IsNull() && dsfi != "TFileInfo") {
            nxpl.Reset();
            while ((plet = (TDSetPlet *) nxpl())) {
               if (dsfi == plet->GetName()) {
                  fn = fi->GetCurrentUrl()->GetUrl();
                  if (plet->fDSet && plet->fDSet->GetListOfElements() &&
                      !(plet->fDSet->GetListOfElements()->FindObject(fn))) plet->fFiles += 1;
                  plet->fMissing += 1;
                  break;
               }
            }
         }
      }
   }

   // Now we can prepare the input for SendParameters
   TList values;
   TString ent("dsn,querytag,querybegin,numfiles,missfiles");
   if (fDataSetInfoVrs == 0) ent = "dsn,querytag,numfiles,missfiles";
   values.Add(new TObjString(ent.Data()));
   nxpl.Reset();
   while ((plet = (TDSetPlet *) nxpl())) {
      if (fDataSetInfoVrs == 0)
         ent.Form("'%s','%s',%d,%d", plet->GetName(), qid, plet->fFiles, plet->fMissing);
      else
         ent.Form("'%s','%s','%s',%d,%d", plet->GetName(), qid, begin, plet->fFiles, plet->fMissing);
      values.Add(new TObjString(ent.Data()));
   }

   PDB(kMonitoring,1)
      Info("SendDataSetInfo", "sending (%d entries)", values.GetSize());

   // Now we are ready to send
   Bool_t rc = fWriter->SendParameters(&values, fDSetSendOpts);

   // Done
   return (rc ? 0 : -1);
}

////////////////////////////////////////////////////////////////////////////////
/// Post information about the requested files. The information is taken
/// from the TDSet object 'dset' and integrated with the missing files
/// information in the list 'missing'. The string 'qid' is the unique
/// ID of the query; 'begin' the starting time.
///
/// The record is formatted for the table 'proofqueryfiles'.
///
/// There are two versions of this record, with or without the starting time.
/// The starting time could be looked up from the summary record, if available.
///
/// The default version 1 corresponds to the table created with the following command:
///
/// CREATE TABLE proofqueryfiles (
///    id int(11) NOT NULL auto_increment,
///    lfn varchar(255) NOT NULL,
///    path varchar(2048) NOT NULL,
///    querytag varchar(64) NOT NULL,
///    querybegin datetime default NULL,
///    status enum('Ok','Failed') NOT NULL default 'Ok',
///    PRIMARY KEY  (id),
///    KEY ix_querytag (querytag) );
///
/// Version 0 corresponds to the table created with the following command:
///    (no 'querybegin')
///
/// CREATE TABLE proofqueryfiles (
///    id int(11) NOT NULL auto_increment,
///    lfn varchar(255) NOT NULL,
///    path varchar(2048) NOT NULL,
///    querytag varchar(64) NOT NULL,
///    status enum('Ok','Failed') NOT NULL default 'Ok',
///    PRIMARY KEY  (id),
///    KEY ix_querytag (querytag) );
///
/// The information is posted with a bulk insert.
///
/// Returns 0 on success, -1 on failure.

Int_t TProofMonSenderSQL::SendFileInfo(TDSet *dset, TList *missing,
                                       const char *begin, const char *qid)
{
   if (!IsValid()) {
      Error("SendFileInfo", "invalid instance: do nothing!");
      return -1;
   }

   // Are we requested to send this info?
   if (!TestBit(TProofMonSender::kSendFileInfo)) return 0;

   // The query id (tag) must be given
   if (!qid || (qid && strlen(qid) <= 0)) {
      Error("SendFileInfo", "query id (tag) undefined!");
      return -1;
   }
   // The dataset must be given
   if (!dset) {
      Error("SendFileInfo", "TDSet object undefined! (qid: '%s')", qid);
      return -1;
   }

   PDB(kMonitoring,1) Info("SendFileInfo", "preparing (qid: '%s')", qid);
   THashList hmiss;
   if (missing) {
      TIter nxfm(missing);
      TFileInfo *fi = 0;
      while ((fi = (TFileInfo *)nxfm())) {
         hmiss.Add(new TObjString(fi->GetCurrentUrl()->GetUrl()));
      }
      PDB(kMonitoring,2) hmiss.Print();
   }

   TList values;
   TString ent("lfn,path,querytag,querybegin,status");
   if (fFileInfoVrs == 0)  ent = "lfn,path,querytag,status";
   values.Add(new TObjString(ent.Data()));

   // Create the file-plets
   TObject *o = 0;
   TDSetElement *e = 0, *ee = 0;
   TDSet *dsete = 0;
   TIter nxe(dset->GetListOfElements());
   TString fne, status;
   while ((o = nxe())) {
      if ((e = dynamic_cast<TDSetElement *>(o))) {
         fne = e->GetName();
         // Try to determine the status
         status = "Ok";
         if (hmiss.FindObject(fne)) status = "Failed";
         if (fFileInfoVrs == 0)
            ent.Form("'%s','%s','%s','%s'", gSystem->BaseName(fne), gSystem->GetDirName(fne).Data(),
                     qid, status.Data());
         else
            ent.Form("'%s','%s','%s','%s','%s'", gSystem->BaseName(fne), gSystem->GetDirName(fne).Data(),
                     qid, begin, status.Data());
         values.Add(new TObjString(ent.Data()));
      } else if ((dsete = dynamic_cast<TDSet *>(o))) {
         PDB(kMonitoring,1)
            Info("SendFileInfo", "dset '%s' (%d files)",
                                 o->GetName(), dsete->GetListOfElements()->GetSize());
         TIter nxee(dsete->GetListOfElements());
         while ((ee = (TDSetElement *) nxee())) {
            fne = ee->GetName();
            // Try to determine the status
            status = "Ok";
            if (hmiss.FindObject(fne)) status = "Failed";
            if (fFileInfoVrs == 0)
               ent.Form("'%s','%s','%s','%s'", gSystem->BaseName(fne), gSystem->GetDirName(fne).Data(),
                        qid, status.Data());
            else
               ent.Form("'%s','%s','%s','%s','%s'", gSystem->BaseName(fne), gSystem->GetDirName(fne).Data(),
                        qid, begin, status.Data());
            values.Add(new TObjString(ent.Data()));
         }
      } else {
         Warning("SendFileInfo", "ignoring unknown element type: '%s'", o->ClassName());
      }
   }

   PDB(kMonitoring,1) Info("SendFileInfo", "sending (%d entries)", values.GetSize());

   // Now we are ready to send
   Bool_t rc = fWriter->SendParameters(&values, fFilesSendOpts);

   // Done
   return (rc ? 0 : -1);
}