/*
  multi-threaded C program that shouldn't leak memory.
  testing for Memory leak using SELECT statements in a multi-threaded application.

  gcc bug62849.c -o bug62849 -g `mysql_config --include` `mysql_config --libs_r`

*/



#include <unistd.h>
#include <pthread.h>

#define TESTTIME (259200)
#define NUMTHREADS (5)
char host[]="127.0.0.1";
int port=3306;
char username[]="root";
char password[]="";
char database[]="callsystem";

#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <mysql.h>
#include <stdio.h>


pid_t         PID;
unsigned long MEM_START, MEM_NOW, MEM_LAST;

pthread_t pthreads[NUMTHREADS];
unsigned long client_version=0;
unsigned long server_version=0;
unsigned long num_queries=0;
int threaddone=0;


int db_query(MYSQL *dbc,char *sql,int showresults);
char* alocmem(size_t num);

// Get memory usage.
unsigned long getmem (int PID)
   {
   struct proc_t {
      int               pid;           // process id
      char              cmd[16];       // basename of executable file in call to exec(2)
      char              state;         // single-char code for process state (S=sleeping)
      int               ppid;          // pid of parent process
      int               pgrp;          // process group id
      int               session;       // session id
      int               tty;           // full device number of controlling terminal
      int               tpgid;         // terminal process group id
      unsigned long     flags;         // kernel flags for the process
      unsigned long     min_flt;       // number of minor page faults since process start
      unsigned long     cmin_flt;      // cumulative min_flt of process and child processes
      unsigned long     maj_flt;       // number of major page faults since process start
      unsigned long     cmaj_flt;      // cumulative maj_flt of process and child processes
      unsigned long     utime;         // user-mode CPU time accumulated by process
      unsigned long     stime;         // kernel-mode CPU time accumulated by process
      long              cutime;        // cumulative utime of process and reaped children
      long              cstime;        // cumulative stime of process and reaped children
      long              priority;      // kernel scheduling priority
      long              nice;          // standard unix nice level of process
      long              timeout;       // ?
      long              it_real_value; // ?
      unsigned long     start_time;    // start time of process -- seconds since 1-1-70
      unsigned long     vsize;         // number of pages of virtual memory ...
      long              rss;           // resident set size from /proc/#/stat
      unsigned long     rss_rlim;      // resident set size ... ?
      unsigned long     start_code;    // address of beginning of code segment
      unsigned long     end_code;      // address of end of code segment
      unsigned long     start_stack;   // address of the bottom of stack for the process
      unsigned long     kstk_esp;      // kernel stack pointer
      unsigned long     kstk_eip;      // kernel stack pointer
      unsigned long     wchan;         // address of kernel wait channel proc is sleeping in
      unsigned long     nswap;         // ?
      unsigned long     cnswap;        // cumulative nswap ?
      int               lproc;         // last processor
   };

   FILE       *memF;
   char        L [500];
   char        pidFN [500];
   struct proc_t      P;

   sprintf (pidFN, "/proc/%i/stat", PID);
   memF = fopen (pidFN, "r");
   if (memF != NULL)
      {
      if (fgets (L, sizeof (L), memF) != NULL)
         {
         char* tmp = strrchr (L, ')');                                          // split into "PID (cmd" and "<rest>"
         *tmp = '\0';                                                           // replace trailing ')' with NUL

         // Parse these two strings separately, skipping the leading "(".
         memset (P.cmd, 0, sizeof (P.cmd));                                     // clear even though *P xcalloc'd ?!
         sscanf (L, "%d (%15c", &P.pid, P.cmd);                                 // comm[16] in kernel

         sscanf (tmp+2, "%c "
                        "%d %d %d %d %d "
                        "%lu %lu %lu %lu %lu %lu %lu "
                        "%ld %ld %ld %ld %ld %ld "
                        "%lu %lu "
                        "%ld "
                        "%lu %lu %lu %lu %lu %lu "
                        "%*s %*s %*s %*s " 
                        "%lu %lu %lu %*d %d",
                        &P.state,
                              &P.ppid, &P.pgrp, &P.session, &P.tty, &P.tpgid,
                        &P.flags, &P.min_flt, &P.cmin_flt, &P.maj_flt, &P.cmaj_flt, &P.utime, &P.stime,
                        &P.cutime, &P.cstime, &P.priority, &P.nice, &P.timeout, &P.it_real_value,
                        &P.start_time, &P.vsize,
                        &P.rss,
                        &P.rss_rlim, &P.start_code, &P.end_code, &P.start_stack, &P.kstk_esp, &P.kstk_eip,
                        &P.wchan, &P.nswap, &P.cnswap, &P.lproc);
         }
      fclose (memF);
      }

   return P.vsize;
   }

void *worker_thread(void *arg)
{
  MYSQL *dbc=NULL;
  my_bool auto_reconnect=1;
  int cancelstate=0;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&cancelstate);

  dbc = mysql_init(NULL);
  if(NULL == dbc)
  {
    printf("mysql_init failed\n");
    goto threadexit;
  }
  else
  {
    if(0!=mysql_options(dbc,MYSQL_OPT_RECONNECT,(char*)&auto_reconnect))
    {
      printf("mysql_options() failed to set MYSQL_OPT_RECONNECT");
    }
    if (!mysql_real_connect(dbc,host,username,password,database,port, NULL, CLIENT_FOUND_ROWS|CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))
    {
      printf("mysql_real_connect failed: %s (%d) (%s)", mysql_error(dbc),mysql_errno(dbc),mysql_sqlstate(dbc));
      mysql_close(dbc);
      dbc=NULL;
    }
  }

  unsigned int counter=0;
  unsigned int repcount=0;
  char shortquery[1024];
  memset(shortquery,0,1024);
  char *longquery;
  longquery=NULL;
  char *c;
  c=NULL;
  while(0==threaddone && NULL!=dbc)
  {
        c=shortquery;
        c+=sprintf(c,"%s"," SELECT IServer,Unit,Script,Node,Type,OnKey1,OnKey2,OnKey3,OnKey4,OnKey5,OnKey6,OnKey7,OnKey8,OnKey9,OnKey0,OnKeyP,OnKeyA,ClearKeys,WaitMess,Digits,TimeOut,OnTimeOut,Tel,Message,Function FROM callsystem.scripts WHERE ((IServer=0 AND Unit=0 AND Script='HIJGEN') OR (IServer=1 AND Unit=0 AND Script='')) AND Node='2002' ORDER BY IServer, Script, Node LIMIT 1;");
        db_query(dbc,shortquery,1);
  }
threadexit:
  mysql_close(dbc);
  mysql_thread_end();
  pthread_exit(0);
}


int main(int argc, const char *argv[])
{
  MYSQL *dbc=NULL;
  int i=0,err=0;

  PID = getpid ();
  MEM_NOW=getmem (PID);
  MEM_LAST=MEM_NOW;
  MEM_START=MEM_NOW;
  printf("PID :%d, MEM:%d\n", PID, getmem (PID));
  
  srand48((unsigned long)1348490240);
  time_t timestart=0,timenow=0;

  unsigned int counter=0;
  counter=0;
  char shortquery[1024]={0};
  char *longquery=NULL;
  longquery=NULL;
  char *c=NULL;
  my_init();
  if (!(dbc = mysql_init(NULL)))
  {
    printf("mysql_init failed\n");
    dbc=NULL;
    goto threadexit;
  }
  else
  {
    if (!mysql_real_connect(dbc,host,username,password,database,port, NULL, CLIENT_FOUND_ROWS|CLIENT_MULTI_STATEMENTS|CLIENT_MULTI_RESULTS))
    {
      printf("mysql_real_connect failed: %s (%d) (%s)", mysql_error(dbc),mysql_errno(dbc),mysql_sqlstate(dbc));
      mysql_close(dbc);
      dbc=NULL;
      goto threadexit;
    }
  }

  printf("running initializations..\n");
  client_version=mysql_get_client_version();
  server_version=mysql_get_server_version(dbc);
  printf("client version=%lu\n",client_version);
  printf("server version=%lu\n",server_version);
  if((client_version/10000) < (server_version/10000))
  {
    printf("different client and server version!  please upgrade client library!\n");
    //goto threadexit;
  }

  if (!mysql_thread_safe())
  {
    printf("non-threadsafe client detected!  please rebuild and link with libmysql_r!\n");
  }

  mysql_close(dbc);

  printf("about to spawn %d threads\n",NUMTHREADS);
  for (i=0;i<NUMTHREADS;i++)
  {
    err=pthread_create(&pthreads[i], NULL, worker_thread, (void *)i);
    if(err!=0)
    {
      printf("error spawning thread %lu, pthread_create returned %lu\n",(unsigned long)i,(unsigned long)err);
    }
    printf(".");
  }
  printf("\n");
  printf("completed spawning new database worker threads\n");

  printf("testcase is now running, so watch for error output\n");

  timestart=time(NULL);
  timenow=time(NULL);
  MEM_NOW=getmem (PID);
  MEM_START=MEM_NOW;
  for(i=0;(timenow-timestart) < TESTTIME;timenow=time(NULL))
  {
    usleep(1000*1000);
    MEM_LAST=MEM_NOW;
    MEM_NOW=getmem (PID);
    printf("queries: %09lu, PID :%d, MEM DIFF:%d\n",num_queries, PID, (MEM_NOW-MEM_START));
  }
  threaddone=1;

  printf("waiting for worker threads to finish...\n");

  for (i=0;i<NUMTHREADS;i++)
  {
    pthread_join(pthreads[i], NULL);
  }

  exit(0);
threadexit:
  exit(-1);
}


int db_query(MYSQL *dbc,char *sql,int showresults)
{
  int res=0;
  MYSQL_RES *r=NULL;
  MYSQL_ROW w;
  MYSQL_FIELD *field=NULL;
  int moreresult=0;
  unsigned int i=0;
  unsigned int myerrno=0;
  if(NULL == dbc) return 0;

  res = mysql_query(dbc,sql);
  if(res != 0 && showresults > 0)
  {
    myerrno=mysql_errno(dbc);
    printf("query failed '%s' : %d (%s) (%s)\n",sql,myerrno,mysql_error(dbc),mysql_sqlstate(dbc));
    return 0;
  }

  num_queries++;
  do
  {
    r = mysql_use_result(dbc);
    if(r)
    {
      unsigned int numfields = mysql_num_fields(r);
      //unsigned int numrows=mysql_num_rows(r);
      while(0!=(field = mysql_fetch_field(r)))
      {
          //print metadata information about each field
          if(showresults > 1)
          {
            printf("%s  ",field->name);
          }
      }
      if(showresults > 1)
      {
        printf("\n------------------------------------\n");
      }

      while (0!=(w = mysql_fetch_row(r)))
      {
        for(i = 0; i < numfields; i++)
        {
          //print each field here
          if(showresults > 1)
          {
            printf("%s\t",w[i]);
          }
        }

        if(showresults > 1)
        {
            printf("\n");
        }
      }
      if(showresults > 1)
      {
        printf("\n");
      }
      mysql_free_result(r);
    }
    else //no rows returned. was it a select?
    {
      if(mysql_field_count(dbc) > 0 && showresults > 0)
      {
        printf("No results for '%s'.  (%d) - %s (%s)\n",sql,mysql_errno(dbc),mysql_error(dbc),mysql_sqlstate(dbc));
        return 0;
      }
      else //it could have been some insert/update/delete
      {
        //this is successful query
      }
    }
    moreresult=mysql_next_result(dbc);
    if(moreresult > 0 && showresults > 0)
    {
      printf("mysql_next_result returned %d, mysql error %s, (%d) (%s)\n",moreresult,mysql_error(dbc),mysql_errno(dbc),mysql_sqlstate(dbc));
      return 0;
    }
  } while (0==moreresult);
  return 1;
}

