/* profile - profile operating system
 *
 * The profile command is used to control Statistical and Call Profiling.
 * It writes the profiling data collected by the kernel to a file.
 *
 * Changes:
 *   14 Aug, 2006   Created (Rogier Meurs)
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#undef SPROFILE
#define SPROFILE 1
#include <minix/profile.h>

#define EHELP			1
#define ESYNTAX			2
#define EMEM			3
#define EOUTFILE		4
#define EFREQ			5
#define EACTION			6	

#define START			1
#define STOP			2
#define GET			3
#define RESET			4

#define SPROF			(action==START||action==STOP)
#define CPROF			(!SPROF)
#define DEF_OUTFILE_S		"profile.stat.out"
#define DEF_OUTFILE_C		"profile.call.out"
#define DEF_OUTFILE		(SPROF?DEF_OUTFILE_S:DEF_OUTFILE_C)
#define NPIPE			"/tmp/profile.npipe"
#define MIN_MEMSIZE		1
#define DEF_MEMSIZE		64
#define MIN_FREQ		3
#define MAX_FREQ		15
#define DEF_FREQ		6
#define BUFSIZE			1024
#define MB			(1024*1024)
#define SYNCING			"SYNC"
#define DEV_LOG			"/dev/log"

int action = 0;
int mem_size = 0;
int mem_used = 0;
int freq = 0;
int intr_type = PROF_RTC;
char *outfile = "";
char *mem_ptr;
int outfile_fd, npipe_fd;
struct sprof_info_s sprof_info;
struct cprof_info_s cprof_info;

#define HASH_MOD	128
struct sproc {
	endpoint_t	ep;
	char		name[8];
	struct sproc *	next;
};

static struct sproc * proc_hash[HASH_MOD];

_PROTOTYPE(int handle_args, (int argc, char *argv[]));
_PROTOTYPE(int start, (void));
_PROTOTYPE(int stop, (void));
_PROTOTYPE(int get, (void));
_PROTOTYPE(int reset, (void));
_PROTOTYPE(int create_named_pipe, (void));
_PROTOTYPE(int alloc_mem, (void));
_PROTOTYPE(int init_outfile, (void));
_PROTOTYPE(int write_outfile, (void));
_PROTOTYPE(int detach, (void));

int main(int argc, char *argv[])
{
  int res;

  if (res = handle_args(argc, argv)) {
	switch(res) {
		case ESYNTAX:
			printf("Error in parameters.\n");
			return 1;
			break;
		case EACTION:
			printf("Specify one of start|stop|get|reset.\n");
			return 1;
			break;
		case EMEM:
			printf("Incorrect memory size.\n");
			return 1;
			break;
		case EFREQ:
			printf("Incorrect frequency.\n");
			return 1;
			break;
		case EOUTFILE:
			printf("Output filename missing.\n");
			return 1;
			break;
		default:
			break;
	}

	/*
	 * Check the frequency when we know the intr type. Only selected values
	 * are correct for RTC
	 */
	if (action == START && intr_type == PROF_RTC && 
			(freq < MIN_FREQ || freq > MAX_FREQ)) {
		printf("Incorrect frequency.\n");
		return 1;
	}

        printf("Statistical Profiling:\n");
	printf("  profile start [--rtc | --nmi] "
			"[-m memsize] [-o outfile] [-f frequency]\n");
        printf("  profile stop\n\n");
	printf("   --rtc is default, --nmi allows kernel profiling\n");
        printf("Call Profiling:\n");
	printf("  profile get   [-m memsize] [-o outfile]\n");
        printf("  profile reset\n\n");
	printf("   - memsize in MB, default: %u\n", DEF_MEMSIZE);
	printf("   - default output file: profile.{stat|call}.out\n");
	printf( "   - sample frequencies for --rtc (default: %u):\n", DEF_FREQ);
	printf("      3    8192 Hz          10     64 Hz\n");
	printf("      4    4096 Hz          11     32 Hz\n");
	printf("      5    2048 Hz          12     16 Hz\n");
	printf("      6    1024 Hz          13      8 Hz\n");
	printf("      7     512 Hz          14      4 Hz\n");
	printf("      8     256 Hz          15      2 Hz\n");
	printf("      9     128 Hz\n\n");
	printf("Use [sc]profalyze.pl to analyze output file.\n");
	return 1;
  }

  switch(action) {
	  case START:
	  	if (start()) return 1;
		break;
	  case STOP:
	  	if (stop()) return 1;
		break;
	  case GET:
	  	if (get()) return 1;
		break;
	  case RESET:
	  	if (reset()) return 1;
		break;
	  default:
		break;
  }
  return 0;
}


int handle_args(int argc, char *argv[])
{
  while (--argc) {
	++argv;

	if (strcmp(*argv, "-h") == 0 || strcmp(*argv, "help") == 0 ||
		strcmp(*argv, "--help") == 0) {
		return EHELP;
	} else
	if (strcmp(*argv, "-m") == 0) {
		if (--argc == 0) return ESYNTAX;
		if (sscanf(*++argv, "%u", &mem_size) != 1 ||
			mem_size < MIN_MEMSIZE ) return EMEM;
	} else
	if (strcmp(*argv, "-f") == 0) {
		if (--argc == 0) return ESYNTAX;
		if (sscanf(*++argv, "%u", &freq) != 1)
			return EFREQ;
	} else
	if (strcmp(*argv, "-o") == 0) {
		if (--argc == 0) return ESYNTAX;
		outfile = *++argv;
	} else
	if (strcmp(*argv, "--rtc") == 0) {
		intr_type = PROF_RTC;
	} else
	if (strcmp(*argv, "--nmi") == 0) {
		intr_type = PROF_NMI;
	} else
	if (strcmp(*argv, "start") == 0) {
		if (action) return EACTION;
	       	action = START;
	} else
	if (strcmp(*argv, "stop") == 0) {
		if (action) return EACTION;
	       	action = STOP;
	} else
	if (strcmp(*argv, "get") == 0) {
		if (action) return EACTION;
	       	action = GET;
	} else
	if (strcmp(*argv, "reset") == 0) {
		if (action) return EACTION;
	       	action = RESET;
	}
  }
  
  /* No action specified. */
  if (!action) return EHELP;

  /* Init unspecified parameters. */
  if (action == START || action == GET) {
	if (strcmp(outfile, "") == 0) outfile = DEF_OUTFILE;
	if (mem_size == 0) mem_size = DEF_MEMSIZE;
	mem_size *= MB;				   /* mem_size in bytes */
  }
  if (action == START) {
	mem_size -= mem_size % sizeof(struct sprof_sample); /* align to sample size */
	if (freq == 0) freq = DEF_FREQ;		   /* default frequency */
  }
  return 0;
}      


int start()
{
 /* This is the "starter process" for statistical profiling.
  *
  * Create output file for profiling data.  Create named pipe to
  * synchronize with stopper process.  Fork so the parent can exit.
  * Allocate memory for profiling data.  Start profiling in kernel.
  * Complete detachment from terminal.  Write known string to named
  * pipe, which blocks until read by stopper process.  Redirect
  * stdout/stderr to the named pipe.  Write profiling data to file.
  * Clean up.
  */
  int log_fd;

  if (init_outfile() || create_named_pipe()) return 1;

  printf("Starting statistical profiling.\n");

  if (fork() != 0) exit(0);

  if (alloc_mem()) return 1;

  if (sprofile(PROF_START, mem_size, freq, intr_type, &sprof_info, mem_ptr)) {
	perror("sprofile");
	printf("Error starting profiling.\n");
	return 1;
  }

  detach();

  /* Temporarily redirect to system log to catch errors. */
  log_fd = open(DEV_LOG, O_WRONLY);
  dup2(log_fd, 1);
  dup2(log_fd, 2);

  if ((npipe_fd = open(NPIPE, O_WRONLY)) < 0) {
	printf("Unable to open named pipe %s.\n", NPIPE);
	return 1;
  } else
	/* Synchronize with stopper process. */
	write(npipe_fd, SYNCING, strlen(SYNCING));

  /* Now redirect to named pipe. */
  dup2(npipe_fd, 1);
  dup2(npipe_fd, 2);

  mem_used = sprof_info.mem_used;

  if (mem_used == -1) {
	  printf("WARNING: Profiling was stopped prematurely due to ");
	  printf("insufficient memory.\n");
	  printf("Try increasing available memory using the -m switch.\n");
  }

  if (write_outfile()) return 1;

  close(log_fd);
  close(npipe_fd);
  unlink(NPIPE);
  close(outfile_fd);
  free(mem_ptr);

  return 0;
}


int stop()
{
  /* This is the "stopper" process for statistical profiling.
   *
   * Stop profiling in kernel.  Read known string from named pipe
   * to synchronize with starter proces.  Read named pipe until EOF
   * and write to stdout, this allows feedback from started process
   * to be printed.
   */
  int n;
  char buf[BUFSIZE];

  if (sprofile(PROF_STOP, 0, 0, 0, 0, 0)) {
	perror("sprofile");
	printf("Error stopping profiling.\n");
  	return 1;
  } else printf("Statistical profiling stopped.\n");

  if ((npipe_fd = open(NPIPE, O_RDONLY)) < 0) {
	printf("Unable to open named pipe %s.\n", NPIPE);
	return 1;
  } else
	/* Synchronize with starter process. */
	read(npipe_fd, buf, strlen(SYNCING));

  while ((n = read(npipe_fd, buf, BUFSIZE)) > 0)
	write(1, buf, n);

  close(npipe_fd);

  return 0;
}


int get()
{
 /* Get function for call profiling.
  *
  * Create output file.  Allocate memory.  Perform system call to get
  * profiling table and write it to file.  Clean up.
  */
  if (init_outfile()) return 1;

  printf("Getting call profiling data.\n");

  if (alloc_mem()) return 1;

  if (cprofile(PROF_GET, mem_size, &cprof_info, mem_ptr)) {
	perror("cprofile");
	printf("Error getting data.\n");
	return 1;
  }

  mem_used = cprof_info.mem_used;

  if (mem_used == -1) {
	printf("ERROR: unable to get data due to insufficient memory.\n");
	printf("Try increasing available memory using the -m switch.\n");
  } else
  if (cprof_info.err) {
	printf("ERROR: the following error(s) happened during profiling:\n");
	if (cprof_info.err & CPROF_CPATH_OVERRUN)
		printf(" call path overrun\n");
	if (cprof_info.err & CPROF_STACK_OVERRUN)
		printf(" call stack overrun\n");
	if (cprof_info.err & CPROF_TABLE_OVERRUN)
		printf(" hash table overrun\n");
	printf("Try changing values in /usr/src/include/minix/profile.h ");
	printf("and then rebuild the system.\n");
  } else
  if (write_outfile()) return 1;

  close(outfile_fd);
  free(mem_ptr);

  return 0;
}


int reset()
{
 /* Reset function for call profiling.
  *
  * Perform system call to reset profiling table.
  */
  printf("Resetting call profiling data.\n");

  if (cprofile(PROF_RESET, 0, 0, 0)) {
	perror("cprofile");
	printf("Error resetting data.\n");
	return 1;
  }

  return 0;
}


int alloc_mem()
{
  if ((mem_ptr = malloc(mem_size)) == 0) {
	printf("Unable to allocate memory.\n");
	printf("Used chmem to increase available proces memory?\n");
	return 1;
  } else memset(mem_ptr, '\0', mem_size);

  return 0;
}


int init_outfile()
{
  if ((outfile_fd = open(outfile, O_CREAT | O_TRUNC | O_WRONLY)) <= 0) {
	printf("Unable to create outfile %s.\n", outfile);
	return 1;
  } else chmod(outfile, S_IRUSR | S_IWUSR);

  return 0;
}


int create_named_pipe()
{
  if ((mkfifo(NPIPE, S_IRUSR | S_IWUSR) == -1) && (errno != EEXIST)) {
	printf("Unable to create named pipe %s.\n", NPIPE);
	return 1;
  } else
	return 0;
}


int detach()
{
  setsid();
  (void) chdir("/");
  close(0);
  close(1);
  close(2);
}

static void add_proc(struct sprof_proc * p)
{
	struct sproc * n;
	int slot = ((unsigned)(p->proc)) % HASH_MOD;

	n = malloc(sizeof(struct sproc));
	if (!n)
		abort();
	n->ep = p->proc;
	memcpy(n->name, p->name, 8);
	n->next = proc_hash[slot];
	proc_hash[slot] = n;
}

static char * get_proc_name(endpoint_t ep)
{
	struct sproc * p;

	for (p = proc_hash[((unsigned)ep) % HASH_MOD]; p; p = p->next) {
		if (p->ep == ep)
			return p->name;
	}

	return NULL;
}

int write_outfile()
{
  int n, towrite, written = 0;
  char *buf = mem_ptr;
  char header[80];
  struct sprof_sample *sample;

  printf("Writing to %s ...", outfile);

  /* Write header. */
  if (SPROF)
	sprintf(header, "stat\n%d %d %d %d\n",	sprof_info.total_samples,
						sprof_info.idle_samples,
					  	sprof_info.system_samples,
					  	sprof_info.user_samples);
  else
	sprintf(header, "call\n%u %u\n",
				CPROF_CPATH_MAX_LEN, CPROF_PROCNAME_LEN);

  n = write(outfile_fd, header, strlen(header));

  if (n < 0) { printf("Error writing to outfile %s.\n", outfile); return 1; }

  /* Write data. */
  towrite = mem_used == -1 ? mem_size : mem_used;

  sample = (struct sprof_sample *) mem_ptr;
  while (towrite > 0) {
	  unsigned bytes;
	  char	entry[12];
	  char * name;

	  name = get_proc_name(sample->proc);
	  if (!name) {
		  add_proc((struct sprof_proc *)sample);
		  bytes = sizeof(struct sprof_proc);
		  towrite -= bytes;
		  sample = (struct sprof_sample *)(((char *) sample) + bytes);
		  continue;
	  }

	  memset(entry, 0, 12);
	  memcpy(entry, name, strlen(name));
	  memcpy(entry + 8, &sample->pc, 4);

	  if (write(outfile_fd, entry, 12) != 12) {
		  printf("Error writing to outfile %s.\n", outfile);
		  return 1;
	  }
	  towrite -= sizeof(struct sprof_sample);
	  sample++;

	  written += 12;
  }

  printf(" header %d bytes, data %d bytes.\n", strlen(header), written);

  return 0;
}

