/*========================================================================
 * Copyright (C) VMware, Inc. 1986-2008.  All Rights Reserved.
 *
 *  Name - gsstat.c - a sample program for the gcsi interface
 *
 * $Id: gsstat.cc,v 1.2 2008-01-09 22:50:06 stever Exp $
 *
 *========================================================================
 */
#include "flag.ht"

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "shrpcstats.ht"
#include "gcsierr.ht"
#include "gcsi.hf"

class GsCacheConnection {
public:
  char errBuf[200];
  char cacheName[200];
  char stoneName[200];
  char hostname[200];
  char ipAddress[64];
  int interval;
  int linesPrinted;

  BoolType initFromArgs(int argc, char *argv[]);
  BoolType printStats(void);
  void printBannerIfNeeded(void);
  void printBanner(void);
  void serviceLoop(void);
  BoolType attachCache(void);

private:
  BoolType setCacheName(const char *nameFromUser);
  BoolType getHostIpv4Address(void);
};


// returns a pointer to a string which represents the IP v4
// address of this host.  This routine is NOT thread-safe,
// but should be quite portable :-)
BoolType GsCacheConnection::getHostIpv4Address(void)
{
  if (gethostname(hostname, sizeof(hostname))) {
    perror("gethostname failed");
    return FALSE;
  }
   
  struct hostent* host = gethostbyname(hostname);
  if (host == NULL) {
    perror("gethostbyname failed");
    return FALSE;
  }

  // only try the first address in the list
  struct in_addr in;
  memcpy(&in.s_addr, host->h_addr_list[0], sizeof(in.s_addr)); 

  char *result = inet_ntoa(in);
  if (result == NULL) {
    perror("inet_toa failed");
    return FALSE;
  }

  strncpy(ipAddress, result, sizeof(ipAddress));
  return TRUE;
}

BoolType GsCacheConnection::attachCache(void)
{
  int result = GcsiAttachSharedCache(cacheName, errBuf, sizeof(errBuf));
  return result == GCSI_SUCCESS;
}

BoolType GsCacheConnection::setCacheName(const char *nameFromUser)
{
  strncpy(stoneName, nameFromUser, sizeof(stoneName));
  const char *atSign = strchr(nameFromUser, '@');
  if (atSign != NULL) { // this is a full cache name
    stoneName[atSign - nameFromUser] = '\0';
    strncpy(cacheName, nameFromUser, sizeof(cacheName));
  }
  else { // th is is only the stonename, build the cache name from the IP
    sprintf(cacheName, "%s@%s", nameFromUser, ipAddress);
  }

  return TRUE;
}

BoolType GsCacheConnection::initFromArgs(int argc, char *argv[])
{
  memset(this, 0, sizeof(*this));
  linesPrinted = -1;
  GcsiInit();
  if (!getHostIpv4Address())
    return FALSE;

  if (!setCacheName(argv[1]))
    return FALSE;

  if (argc == 2) // print 1 sample only
    interval = -1;
  else {
    interval = atoi(argv[2]);
    if (interval < 1)
      return FALSE;
  }
  return TRUE;
}

void GsCacheConnection::printBanner(void)
{
  printf("\n");
  printf("Sess     CR    PNR        PD       DNR      FP OCS     FF\n");
  printf("---------------------------------------------------------\n");
}


void GsCacheConnection::printBannerIfNeeded(void)
{
  if (linesPrinted != 20 && linesPrinted != -1)
    return;
  
  printBanner();
  linesPrinted = 0;
}


BoolType GsCacheConnection::printStats(void)
{
  GcsiResultSType stnStats;
  int numStats = 2;
  unsigned int numCr = 0; // commit records
  unsigned int pnr = 0; // pages need reclaiming
  uint64_t pd = 0; // possible dead
  uint64_t dnr = 0; // dead not reclaimed
  unsigned int sess = 0; // number of sessions on stone
  unsigned int fp = 0; // free pages in stone
  int oldCr = 0; // oldest CR session


  unsigned int mask = SHRPC_STONE;
  int result = GcsiAllStatsForMask(mask, &stnStats, &numStats);
  BoolType hasStone = TRUE;

  switch (result) {
  case GCSI_SUCCESS:
    break;

  case GCSI_ERR_NO_STONE:
    hasStone = FALSE;
    break;
 
  default:
    return FALSE;
    break;
  }

  if (hasStone) {
    ShrPcStnStatSType *s = &stnStats.stats.u.stone;
    sess = s->totalSessionsCount;
    numCr = s->numberOfCommitRecords;
    pnr = s->pagesNeedReclaimingSize;
    pd = 1024 * (uint64_t)s->possibleDeadKobjs;
    dnr = 1024 * (uint64_t)s->deadNotReclaimedKobjs;
    fp = s->freePages;
    oldCr = s->oldestCrSession;
    if (oldCr == 0)
      oldCr = s->oldestCrSessNotInTrans;
  }

  // now get the free frame couont
  unsigned int ff = 0;
  result = GcsiShrPcMonStatAtOffset(offsetof(ShrPcMonStatSType, numberOfFreeFrames), &ff);
  if (result != GCSI_SUCCESS)
    return FALSE;


 if (hasStone) 
   printf("%4u %6u %6u %9lu %9lu %7u %3u %6u\n", sess, numCr, pnr, pd, dnr, fp, oldCr, ff);
 else // remote shared cache, no stone stats available
   printf("   ?      ?      ?         ?         ?        ?    ? %6u\n", ff);

 fflush(stdout);
 ++linesPrinted;
 return TRUE;
}

void GsCacheConnection::serviceLoop(void)
{
  int sleepMs = interval * 1000;
  for (;;) {
    printBannerIfNeeded();
    if (!printStats()) {
      printf("  *** cache shutdown detected.  Goodbye. ***\n");
      break;
    }
    if (poll(NULL, 0, sleepMs) == -1)
      break; // probably a ctrl-C signal, die now
  }
}

static void usage(char *argv[])
{
  fprintf(stderr, "Usage: %s <cache or stone name> <interval>\n", argv[0]);
  exit(1);
}

int main(int argc, char *argv[])
{
  int i;
  int exitCode = 0;
  if (argc < 2 || argc > 3) 
    usage(argv); // does not return

  GsCacheConnection conn;
  if (!conn.initFromArgs(argc, argv))
    usage(argv); // does not return

  if (!conn.attachCache()) {
    printf("Attach to cache named %s failed\n", 
	   conn.cacheName);
    exit(1);
  }

  if (conn.interval == -1) {
    conn.printBanner();
    conn.printStats();
    exit(0);
  }

  conn.serviceLoop();
  exit(0);
}


