/*
 * Copyright (c) 1996-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/* squid-top.c */

/* Copyright 1997-2000 by Eberhard Mattes <em-gw@windhager.de>
   Donated to the public domain.  No warranty.

   1997-08-26 Initial version
   1999-07-02 Add options -c, -d, and -t; ignore case of request method
   2000-07-07 Use <unistd.h> instead of <getopt.h> */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include "libemfw.h"
#include "emio.h"
#include "squid-gw.h"
#include "tables.h"

extern  size_t 	strlcat(char*,const char*,size_t);
extern  size_t 	strlcpy(char*,const char*,size_t);

#define ISDIGIT(c) ((c) >= '0' && (c) <= '9')

struct entry
{
  struct entry *next;		/* Hash bucket */
  char *name;			/* host / content type / client  */
  long port;
  long count;
  long bytes;
};

#define HASH_SIZE 997

static struct entry *hash_table[HASH_SIZE];
static long entry_count;
static int max_entry_length;
static int by_requests;
static int by_bytes;
static enum { ST_DEST, ST_CLIENT, ST_CTYPE } stat_type;

static void usage (void)
{
  puts ("Usage: squid-top [-c|-d|-t] [-b] [-r] -n <number>");
  exit (1);
}

static void init (void)
{
  int i;

  for (i = 0; i < HASH_SIZE; ++i)
    hash_table[i] = NULL;
}

static void add (const char *name, int name_len, long port, long bytes)
{
  struct entry *p;
  unsigned h;

  h = hash (name, name_len, HASH_SIZE);
  for (p = hash_table[h]; p != NULL; p = p->next)
    if (p->port == port
	&& strncmp (p->name, name, name_len) == 0
	&& p->name[name_len] == 0)
      break;
  if (p == NULL)
    {
      int len;

      p = xmalloc (sizeof (struct entry));
      p->next = hash_table[h];
      hash_table[h] = p;
      p->name = xstrndup (name, name_len);
      p->port = port;
      p->count = 0; p->bytes = 0;
      ++entry_count;
      len = name_len + 1;
      if (stat_type == ST_DEST)
	{
	  char tmp[100];
	  snprintf (tmp, 99, "%ld", port);
	  len += strlen (tmp);	/* Don't trust sprintf()'s return value */
	}
      if (len > max_entry_length)
	max_entry_length = len;
    }
  ++p->count; p->bytes += bytes;
}

static void load (void)
{
  static char buf[65536];
  size_t len;
  char *s, *start, *name;
  struct url u;
  int c, nn, name_len;
  long bytes, port = 0;

  while (fgets (buf, sizeof (buf), stdin) != NULL)
    {
      len = strlen (buf);
      if (len == 0 || buf[len-1] != '\n')
	{
	  do
	    {
	      c = getchar ();
	    } while (c != EOF && c != '\n');
	  if (c == EOF)
	    break;
	  continue;
	}
      buf[len-1] = 0;
      name = NULL; name_len = 0;

      /* HOST/IP-ADDR */

      s = strchr (buf, '\t');
      if (s == NULL)
	continue;
      if (stat_type == ST_CLIENT)
	{
	  name = buf; name_len = s - buf;
	}
      ++s;

      /* Request method */

      if (strncasecmp (s, "GET ", 4) == 0)
	s += 4;
      else if (strncasecmp (s, "POST ", 5) == 0
	       || strncasecmp (s, "HEAD ", 5) == 0)
	s += 5;
      else
	continue;

      /* URL */

      while (*s == ' ')
	++s;
      start = s;
      while (*s != 0 && *s != ' ')
	++s;
      if (url_parse (&u, (octet*) start, s - start, 0) != 0)
	continue;
      if (u.host_length == 0 || u.port == -1)
	continue;
      if (stat_type == ST_DEST)
	{
	  name = start + u.host_start; name_len = u.host_length;
	  port = u.port;
	}
      while (*s == ' ')
	++s;
      if (strncasecmp (s, "HTTP/", 5) != 0)
	continue;

      /* HTTP/x.y */

      s = strchr (s, '\t');
      if (s == NULL)
	continue;
      ++s;

      /* Content-Type */

      start = s;
      s = strchr (s, '\t');
      if (s == NULL)
	continue;
      if (stat_type == ST_CTYPE)
	{
	  char *sep;
	  name = start; name_len = s - start;
	  sep = memchr (name, ';', name_len);
	  if (sep != NULL)
	    name_len = sep - start;
	}
      ++s;

      /* Statistics */

      nn = 1;
      while (nn != 0 && (*s == ' ' || ISDIGIT (*s)))
	{
	  if (*s == ' ')
	    --nn;
	  ++s;
	}
      bytes = 0;
      if (nn == 0 && ISDIGIT (*s))
	while (ISDIGIT (*s))
	  bytes = bytes * 10 + *s++ - '0';

      /* Insert into hash table. */

      if (name == NULL) abort();
      lower_copy (name, name, name_len);
      add (name, name_len, port, bytes);
    }
}

/* Heapsort. */

static void sift (const struct entry **tab, int left, int right,
		  int (*cmp)(const struct entry *, const struct entry *))
{
  int i, j;
  const struct entry *p;

  i = left; j = 2 * i + 1; p = tab[i];
  while (j <= right)
    {
      if (j < right && cmp (tab[j], tab[j+1]) < 0)
	++j;
      if (cmp (p, tab[j]) >= 0)
	break;
      tab[i] = tab[j]; i = j; j = 2 * i + 1;
    }
  tab[i] = p;
}

static void sort_and_report (int (*cmp)(const struct entry *,
					const struct entry *), long n)
{
  unsigned h;
  int left, right, i;
  const struct entry *p;
  const struct entry **tab;
  char *name;

  tab = xmalloc (entry_count * sizeof (struct entry *));
  name = xmalloc (max_entry_length + 1);
  i = 0;
  for (h = 0; h < HASH_SIZE; ++h)
    for (p = hash_table[h]; p != NULL; p = p->next)
      tab[i++] = p;
  if (i != entry_count)
    abort ();
  left = i / 2; right = i - 1;
  while (left > 0)
    {
      --left;
      sift (tab, left, right, cmp);
    }
  while (right >= 0 && n > 0)
    {
      p = tab[0];
      if (stat_type == ST_DEST)
	snprintf (name, max_entry_length, "%s:%ld", p->name, p->port);
      else
	strlcpy (name, p->name, max_entry_length);  /* TODO: don't copy */
      if (strlen (name) > max_entry_length)
	abort ();
      printf ("%-*s %10ld %10ld\n",
	      max_entry_length, name, p->count, p->bytes);
      p = tab[left]; tab[left] = tab[right]; tab[right] = p;
      --right; --n;
      if (right > 0 && n > 0)
	sift (tab, left, right, cmp);
    }

  free (tab);
  free (name);
}

static int cmp_count (const struct entry *h1, const struct entry *h2)
{
  if (h1->count < h2->count)
    return -1;
  else if (h1->count > h2->count)
    return 1;
  else if (h1->bytes < h2->bytes)
    return -1;
  else if (h1->bytes > h2->bytes)
    return 1;
  else
    return 0;
}

static int cmp_bytes (const struct entry *h1, const struct entry *h2)
{
  if (h1->bytes < h2->bytes)
    return -1;
  else if (h1->bytes > h2->bytes)
    return 1;
  else if (h1->count < h2->count)
    return -1;
  else if (h1->count > h2->count)
    return 1;
  else
    return 0;
}

static void report (long n)
{
  if (by_bytes)
    {
      printf ("Top %ld by number of bytes:\n\n", n);
      sort_and_report (cmp_bytes, n);
    }
  if (by_requests)
    {
      if (by_bytes)
	putchar ('\n');
      printf ("Top %ld by number of requests:\n\n", n);
      sort_and_report (cmp_count, n);
    }
}

int main (int argc, char *argv[])
{
  long n = 0;
  char *end;
  int c;

  by_bytes = by_requests = 0;
  stat_type = ST_DEST;
  while ((c = getopt (argc, argv, "bcdn:rt")) != -1)
    switch (c)
      {
      case 'b':
	by_bytes = 1;
	break;
      case 'c':
	stat_type = ST_CLIENT;
	break;
      case 'd':
	stat_type = ST_DEST;
	break;
      case 'n':
	errno = 0;
	n = strtol (optarg, &end, 10);
	if (n < 1 || errno != 0 || *end != 0)
	  usage ();
	break;
      case 'r':
	by_requests = 1;
	break;
      case 't':
	stat_type = ST_CTYPE;
	break;
      default:
	usage ();
      }
  if (optind != argc || n < 1)
    usage ();
  if (!by_bytes && !by_requests)
    by_bytes = by_requests = 1;

  init ();
  load ();
  report (n);

  return 0;
}
