/********************************************************
 * File: crashstats.c
 * Created at Sun Jan 28 22:10:33 MSK 2001 by raorn // raorn@binec.ru
 * CrashStats - statistics generation utility
 * $Id: crashstats.c,v 1.17 2002/01/10 02:04:09 raorn Exp $
 *******************************************************/
#include <machine/defs.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#elif HAVE_SYS_TIME
# include <sys/time.h>
#else
# include <time.h>
#endif
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#else
# include <shared/getopt.h>
#endif

#include <shared/jblist.h>
#include <shared/node4d.h>

uchar verstr[] = "CrashStats/" PLATFORM_NAME " " VERSION;

#define STATS_IDENTIFIER   "CST3"

struct DiskAreaStats {
  uchar Tagname[80];
  struct Node4D Aka;

  uchar Group;
  uchar fill_to_make_even;      /* Just ignore this one */

  ulong TotalTexts;
  ushort Last8Days[8];
  ulong Dupes;

  time_t FirstTime;
  time_t LastTime;
};

struct DiskNodeStats {
  struct Node4D Node;
  ulong GotNetmails;
  ulong GotNetmailBytes;
  ulong SentNetmails;
  ulong SentNetmailBytes;
  ulong GotEchomails;
  ulong GotEchomailBytes;
  ulong SentEchomails;
  ulong SentEchomailBytes;
  ulong Dupes;
  time_t FirstTime;
};

struct StatsNode {
  struct StatsNode *Next;
  uchar Tagname[80];
  ulong Average;
  ulong Total;
  ulong Dupes;
  time_t FirstTime;
  time_t LastTime;
  ushort Last8Days[8];
};

struct NodeStatsNode {
  struct NodeStatsNode *Next;
  struct Node4D Node;
  ulong GotNetmails;
  ulong GotNetmailBytes;
  ulong SentNetmails;
  ulong SentNetmailBytes;
  ulong GotEchomails;
  ulong GotEchomailBytes;
  ulong SentEchomails;
  ulong SentEchomailBytes;
  ulong Dupes;
  ulong Days;
  time_t FirstTime;
};

#define SHOWVER   128
#define NOAREAS   129
#define NONODES   130

bool diskfull;

int CompareAlpha(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  return (stricmp((*s1)->Tagname, (*s2)->Tagname));
}

int CompareTotal(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  if ((*s1)->Total < (*s2)->Total)
    return (1);
  if ((*s1)->Total > (*s2)->Total)
    return (-1);
  return (0);
}

int CompareDupes(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  if ((*s1)->Dupes < (*s2)->Dupes)
    return (1);
  if ((*s1)->Dupes > (*s2)->Dupes)
    return (-1);
  return (0);
}

int CompareMsgsDay(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  if ((*s1)->Average < (*s2)->Average)
    return (1);
  if ((*s1)->Average > (*s2)->Average)
    return (-1);
  return (0);
}

int CompareFirstTime(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  if ((*s1)->FirstTime < (*s2)->FirstTime)
    return (1);
  if ((*s1)->FirstTime > (*s2)->FirstTime)
    return (-1);
  return (0);
}

int CompareLastTime(const void *a1, const void *a2)
{
  struct StatsNode **s1, **s2;

  s1 = (struct StatsNode **) a1;
  s2 = (struct StatsNode **) a2;

  if ((*s1)->LastTime < (*s2)->LastTime)
    return (1);
  if ((*s1)->LastTime > (*s2)->LastTime)
    return (-1);
  return (0);
}

bool Sort(struct jbList * list, uchar sortmode)
{
  ulong nc;
  struct StatsNode *sn, **buf, **work;

  nc = 0;

  for (sn = (struct StatsNode *) list->First; sn; sn = sn->Next)
    nc++;

  if (nc == 0)
    return (TRUE);              /* Nothing to sort */

  if (!(buf = (struct StatsNode **) malloc(nc * sizeof(struct StatsNode *))))
    return (FALSE);

  work = buf;

  for (sn = (struct StatsNode *) list->First; sn; sn = sn->Next)
    *work++ = sn;

  switch (sortmode) {
    case 'a':
      qsort(buf, nc, 4, CompareAlpha);
      break;

    case 't':
      qsort(buf, nc, 4, CompareTotal);
      break;

    case 'm':
      qsort(buf, nc, 4, CompareMsgsDay);
      break;

    case 'd':
      qsort(buf, nc, 4, CompareFirstTime);
      break;

    case 'l':
      qsort(buf, nc, 4, CompareLastTime);
      break;

    case 'u':
      qsort(buf, nc, 4, CompareDupes);
      break;
  }

  jbNewList(list);

  for (work = buf; nc--;)
    jbAddNode(list, (struct jbNode *) *work++);

  free(buf);

  return (TRUE);
}

int CompareNodes(const void *a1, const void *a2)
{
  struct NodeStatsNode **s1, **s2;

  s1 = (struct NodeStatsNode **) a1;
  s2 = (struct NodeStatsNode **) a2;

  return (Compare4D(&(*s1)->Node, &(*s2)->Node));
}

bool SortNodes(struct jbList * list)
{
  struct NodeStatsNode *sn, **buf, **work;
  ulong nc;

  nc = 0;

  for (sn = (struct NodeStatsNode *) list->First; sn; sn = sn->Next)
    nc++;

  if (nc == 0)
    return (TRUE);              /* Nothing to sort */

  if (!(buf = (struct NodeStatsNode **) malloc(nc * sizeof(struct NodeStatsNode *))))
    return (FALSE);

  work = buf;

  for (sn = (struct NodeStatsNode *) list->First; sn; sn = sn->Next)
    *work++ = sn;

  qsort(buf, nc, 4, CompareNodes);

  jbNewList(list);

  for (work = buf; nc--;)
    jbAddNode(list, (struct jbNode *) *work++);

  free(buf);

  return (TRUE);
}

char *unit(long i)
{
  static char buf[40];
  if ((i > 10000000) || (i < -10000000))
    snprintf(buf, 40, "%ld MB", i / (1024 * 1024));
  else if ((i > 10000) || (i < -10000))
    snprintf(buf, 40, "%ld KB", i / 1024);
  else
    snprintf(buf, 40, "%ld bytes", i);
  return buf;
}

bool CheckFlags(uchar group, uchar * node)
{
  int c;

  for (c = 0; c < strlen(node); c++) {
    if (toupper(group) == toupper(node[c]))
      return (TRUE);
  }

  return (FALSE);
}

ulong CalculateAverage(ushort * last8array, ulong total, ulong daystatswritten, time_t firstday)
{
  ushort days, c;
  ulong sum;

  if (daystatswritten == 0 || firstday == 0)
    return (0);

  days = daystatswritten - firstday;
  if (days > 7)
    days = 7;

  sum = 0;

  for (c = 1; c < days + 1; c++)
    sum += last8array[c];

  if (days == 0)
    days = 1;

  if (sum == 0 && total != 0) {
    days = daystatswritten - firstday;
    if (days == 0)
      days = 1;

    return (total / days);
  }

  return (sum / days);
}

bool ctrlc;

RETSIGTYPE breakfunc(int x)
{
  ctrlc = TRUE;
}

int main(int argc, char **argv)
{
  FILE *fh;
  ulong total, areas, totaldupes;
  time_t firsttime, t;
  ulong DayStatsWritten;
  uchar buf[200], date[30], date2[30];
  struct DiskAreaStats dastat;
  struct DiskNodeStats dnstat;
  struct StatsNode *sn;
  struct NodeStatsNode *nsn;
  struct jbList StatsList;
  struct jbList NodesList;
  ulong c, num, tot;
  ushort total8days[8];
  uchar sortmode = 'a', *groups = NULL;
  int lastseven = 0, noareas = 0, nonodes = 0;
  struct tm *tp;
  uchar *monthnames[] =
  { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
    "Nov", "Dec", "???"
  };
  static struct option lopts[] = {
    {"help", 0, NULL, 'h'},
    {"sort", required_argument, NULL, 's'},
    {"groups", required_argument, NULL, 'g'},
    {"last7", 0, NULL, '7'},
    {"no-areas", 0, NULL, NOAREAS},
    {"no-nodes", 0, NULL, NONODES},
    {"version", 0, NULL, SHOWVER},
    {0, 0, 0, 0}
  };
  static uchar optstr[] = "+hs:g:7an";
  static uchar helpstr[] =
"Usage: crashstats [OPTION]... FILE\n"
"Display CrashEcho statistics.\n"
"\n"
"  -h, --help            display this help and exit\n"
"  -s, --sort=MODE       specifies the sort mode:\n"
"                          a - sort alphabetically\n"
"                          t - sort by total number of messages\n"
"                          m - sort by msgs/day\n"
"                          d - sort by first time messages were imported\n"
"                          l - sort by last time messages were imported\n"
"                          u - sort by number of dupes\n"
"  -g, --groups=GROUPS   areas in the specified GROUPS are included\n"
"  -7, --last7           displays detailed information about the flow of\n"
"                        messages in areas for the last seven days\n"
"      --no-areas        hide area statistics\n"
"      --no-nodes        hide node statistics\n"
"      --version         print version number and exit\n\n";
  int ch, lopt_index;

  signal(SIGINT, breakfunc);

  while((ch=getopt_long(argc, argv, optstr, lopts, &lopt_index)) != -1){
    switch(ch){
      case 'h':
      case '?':
      default:
        fprintf(stderr, helpstr);
        exit(EXIT_OK);
        /* Not reached */
      case SHOWVER:
        fprintf(stderr, "%s\n", verstr);
        exit(EXIT_OK);
        /* Not reached */
      case 's':
        sortmode = tolower(optarg[0]);
        break;
      case 'g':
        groups = optarg;
        break;
      case '7':
        lastseven = 1;
        break;
      case NOAREAS:
        noareas = 1;
        break;
      case NONODES:
        nonodes = 1;
        break;
    }
  }

  argc -= optind;
  argv += optind;

  if(argc != 1){
    fprintf(stderr, helpstr);
    exit(EXIT_ERROR);
  }

  if (!strchr("amtdlu", sortmode)) {
    fprintf(stderr, "Unknown sort mode %c\n", sortmode);
    exit(EXIT_ERROR);
  }

  if (noareas && nonodes) {
    fprintf(stderr, "Nothing to do\n");
    exit(EXIT_ERROR);
  }

  if (!(fh = fopen(argv[0], "rb"))) {
    fprintf(stderr, "Error opening %s\n", (char *) argv[0]);
    fprintf(stderr, "Error: %s\n", strerror(errno));
    exit(EXIT_ERROR);
  }

  fread(buf, 4, 1, fh);
  buf[4] = 0;

  if (strcmp(buf, STATS_IDENTIFIER) != 0) {
    fprintf(stderr, "Unknown format of stats file\n");
    fclose(fh);
    exit(EXIT_ERROR);
  }

  fread(&DayStatsWritten, sizeof(ulong), 1, fh);

  total = 0;
  totaldupes = 0;
  firsttime = 0;
  areas = 0;

  for (c = 0; c < 8; c++)
    total8days[c] = 0;

  jbNewList(&StatsList);
  jbNewList(&NodesList);

  fread(&num, sizeof(ulong), 1, fh);
  c = 0;

  if (!noareas) {
    while (c < num && fread(&dastat, 1, sizeof(struct DiskAreaStats), fh) == sizeof(struct DiskAreaStats)) {
      if (!groups || CheckFlags(dastat.Group, groups)) {
        if (!(sn = malloc(sizeof(struct StatsNode)))) {
          fprintf(stderr, "Out of memory\n");
          jbFreeList(&StatsList);
          fclose(fh);
          exit(EXIT_ERROR);
        }

        jbAddNode(&StatsList, (struct jbNode *) sn);

        strcpy(sn->Tagname, dastat.Tagname);
        sn->Dupes = dastat.Dupes;
        sn->Total = dastat.TotalTexts;
        sn->FirstTime = dastat.FirstTime;
        sn->LastTime = dastat.LastTime;
        memcpy(&sn->Last8Days[0], &dastat.Last8Days[0], 8 * sizeof(ushort));

        sn->Average = CalculateAverage(&dastat.Last8Days[0], dastat.TotalTexts, DayStatsWritten, sn->FirstTime / (24 * 60 * 60));
      }

      if (dastat.FirstTime != 0)
        if (firsttime == 0 || firsttime > dastat.FirstTime)
          firsttime = dastat.FirstTime;

      c++;
    }
  } else {
    while (c < num && fread(&dastat, 1, sizeof(struct DiskAreaStats), fh) == sizeof(struct DiskAreaStats))
      c++;
  }

  fread(&num, sizeof(ulong), 1, fh);
  c = 0;

  if (!nonodes) {
    while (c < num && fread(&dnstat, 1, sizeof(struct DiskNodeStats), fh) == sizeof(struct DiskNodeStats)) {
      if (!(nsn = malloc(sizeof(struct NodeStatsNode)))) {
        fprintf(stderr, "Out of memory\n");
        jbFreeList(&NodesList);
        jbFreeList(&StatsList);
        fclose(fh);
        exit(EXIT_ERROR);
      }

      jbAddNode(&NodesList, (struct jbNode *) nsn);

      Copy4D(&nsn->Node, &dnstat.Node);

      nsn->GotNetmails = dnstat.GotNetmails;
      nsn->GotNetmailBytes = dnstat.GotNetmailBytes;
      nsn->SentNetmails = dnstat.SentNetmails;
      nsn->SentNetmailBytes = dnstat.SentNetmailBytes;
      nsn->GotEchomails = dnstat.GotEchomails;
      nsn->GotEchomailBytes = dnstat.GotEchomailBytes;
      nsn->SentEchomails = dnstat.SentEchomails;
      nsn->SentEchomailBytes = dnstat.SentEchomailBytes;
      nsn->Dupes = dnstat.Dupes;

      nsn->Days = DayStatsWritten - dnstat.FirstTime % (24 * 60 * 60);
      if (nsn->Days == 0)
        nsn->Days = 1;

      nsn->FirstTime = dnstat.FirstTime;

      if (dnstat.FirstTime != 0)
        if (firsttime == 0 || firsttime > dnstat.FirstTime)
          firsttime = dnstat.FirstTime;

      c++;
    }
  } else {
    while (c < num && fread(&dnstat, 1, sizeof(struct DiskNodeStats), fh) == sizeof(struct DiskNodeStats))
      c++;
  }

  fclose(fh);

  t = (time_t) DayStatsWritten *24 * 60 * 60;

  tp = localtime(&firsttime);
  snprintf(date, 30, "%02d-%s-%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100);

  tp = localtime(&t);
  snprintf(date2, 30, "%02d-%s-%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100);

  printf("\nStatistics from %s to %s\n", date, date2);

  if (!ctrlc && !noareas) {
    Sort(&StatsList, 'a');
    Sort(&StatsList, sortmode);
    printf("\n");

    if (lastseven) {
      printf("Area                             ");

      for (c = 7; c > 0; c--) {
        t = (DayStatsWritten - c) * 24 * 60 * 60;
        tp = localtime(&t);
        printf("   %02d", tp->tm_mday);
      }

      printf("   Total\n============================================================================\n");

      if (!ctrlc) {
        for (sn = (struct StatsNode *) StatsList.First; sn && !ctrlc; sn = sn->Next) {
          tot = 0;

          for (c = 7; c > 0; c--)
            tot += sn->Last8Days[c];

          printf("%-33.33s %4d %4d %4d %4d %4d %4d %4d : %5ld\n", sn->Tagname,
              sn->Last8Days[7], sn->Last8Days[6], sn->Last8Days[5],
              sn->Last8Days[4], sn->Last8Days[3], sn->Last8Days[2],
              sn->Last8Days[1], tot);

          for (c = 7; c > 0; c--)
            total8days[c] += sn->Last8Days[c];

          areas++;
        }

        if (!ctrlc) {
          tot = 0;

          for (c = 7; c > 0; c--)
            tot += total8days[c];

          printf("=============================================================================\n");
          snprintf(buf, 200, "Totally in all %lu areas", areas);

          printf("%-33.33s %4d %4d %4d %4d %4d %4d %4d : %5ld\n", buf,
              total8days[7], total8days[6], total8days[5], total8days[4],
              total8days[3], total8days[2], total8days[1], tot);
        }
      }
    } else {
      printf("Area                           First     Last         Msgs  Msgs/day   Dupes\n");
      printf("============================================================================\n");

      if (!ctrlc) {
        for (sn = (struct StatsNode *) StatsList.First; sn && !ctrlc; sn = sn->Next) {
          if (sn->LastTime == 0) {
            strcpy(date2, " <Never> ");
          } else {
            tp = localtime(&sn->LastTime);
            snprintf(date2, 30, "%02d-%s-%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100);
          }

          if (sn->FirstTime == 0) {
            strcpy(date, " <Never> ");
          } else {
            tp = localtime(&sn->FirstTime);
            snprintf(date, 30, "%02d-%s-%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100);
          }

          for (c = 0; c < 8; c++)
            total8days[c] += sn->Last8Days[c];

          total += sn->Total;
          totaldupes += sn->Dupes;
          areas++;

          printf("%-30.30s %-9.9s %-9.9s %7ld   %7ld %7ld\n", sn->Tagname, date, date2, sn->Total, sn->Average, sn->Dupes);
        }
      }

      if (!ctrlc) {
        printf("============================================================================\n");
        snprintf(buf, 200, "Totally in all %lu areas", areas);
        printf("%-42s         %7ld   %7ld %7ld\n", buf, total, CalculateAverage(&total8days[0], total, DayStatsWritten, firsttime / (24 * 60 * 60)), totaldupes);
      }
    }
  }

  if (!ctrlc && !nonodes) {
    SortNodes(&NodesList);

    printf("\n");
    printf("Nodes statistics\n");
    printf("================\n");

    for (nsn = (struct NodeStatsNode *) NodesList.First; nsn && !ctrlc; nsn = nsn->Next) {
      if (nsn->FirstTime == 0) {
        strcpy(date, "<Never>");
      } else {
        tp = localtime(&nsn->FirstTime);
        snprintf(date, 30, "%02d-%s-%02d", tp->tm_mday, monthnames[tp->tm_mon], tp->tm_year % 100);
      }

      snprintf(buf, 200, "%u:%u/%u.%u", nsn->Node.Zone, nsn->Node.Net, nsn->Node.Node, nsn->Node.Point);

      printf("%-30.40s Statistics since: %s\n\n", buf, date);
      printf("                                  Sent netmails: %lu/%s\n", nsn->SentNetmails, unit(nsn->SentNetmailBytes));
      printf("                              Received netmails: %lu/%s\n", nsn->GotNetmails, unit(nsn->GotNetmailBytes));
      printf("                                 Sent echomails: %lu/%s\n", nsn->SentEchomails, unit(nsn->SentEchomailBytes));
      printf("                             Received echomails: %lu/%s\n", nsn->GotEchomails, unit(nsn->GotEchomailBytes));
      printf("                                          Dupes: %lu\n", nsn->Dupes);
      printf("\n");
    }
  }

  if (ctrlc) {
    printf("*** Break\n");
  } else {
    printf("\n");
  }

  jbFreeList(&StatsList);
  jbFreeList(&NodesList);

  exit(EXIT_OK);
}


syntax highlighted by Code2HTML, v. 0.9.1