559 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			559 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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];
 | |
| 
 | |
| int handle_args(int argc, char *argv[]);
 | |
| int start(void);
 | |
| int stop(void);
 | |
| int get(void);
 | |
| int reset(void);
 | |
| int create_named_pipe(void);
 | |
| int alloc_mem(void);
 | |
| int init_outfile(void);
 | |
| int write_outfile(void);
 | |
| int write_outfile_cprof(void);
 | |
| int write_outfile_sprof(void);
 | |
| void 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");
 | |
| 	fprintf(stderr, "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) {
 | |
| 	fprintf(stderr, "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) {
 | |
| 	  fprintf(stderr, "WARNING: Profiling was stopped prematurely due to ");
 | |
| 	  fprintf(stderr, "insufficient memory.\n");
 | |
| 	  fprintf(stderr, "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");
 | |
| 	fprintf(stderr, "Error stopping profiling.\n");
 | |
|   	return 1;
 | |
|   } else printf("Statistical profiling stopped.\n");
 | |
| 
 | |
|   if ((npipe_fd = open(NPIPE, O_RDONLY)) < 0) {
 | |
| 	fprintf(stderr, "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");
 | |
| 	fprintf(stderr, "Error getting data.\n");
 | |
| 	return 1;
 | |
|   }
 | |
| 
 | |
|   mem_used = cprof_info.mem_used;
 | |
| 
 | |
|   if (mem_used == -1) {
 | |
| 	fprintf(stderr, "ERROR: unable to get data due to insufficient memory.\n");
 | |
| 	fprintf(stderr, "Try increasing available memory using the -m switch.\n");
 | |
|   } else
 | |
|   if (cprof_info.err) {
 | |
| 	fprintf(stderr, "ERROR: the following error(s) happened during profiling:\n");
 | |
| 	if (cprof_info.err & CPROF_CPATH_OVERRUN)
 | |
| 		fprintf(stderr, " call path overrun\n");
 | |
| 	if (cprof_info.err & CPROF_STACK_OVERRUN)
 | |
| 		fprintf(stderr, " call stack overrun\n");
 | |
| 	if (cprof_info.err & CPROF_TABLE_OVERRUN)
 | |
| 		fprintf(stderr, " hash table overrun\n");
 | |
| 	fprintf(stderr, "Try changing values in /usr/src/include/minix/profile.h ");
 | |
| 	fprintf(stderr, "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");
 | |
| 	fprintf(stderr, "Error resetting data.\n");
 | |
| 	return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| int alloc_mem()
 | |
| {
 | |
|   if ((mem_ptr = malloc(mem_size)) == 0) {
 | |
| 	fprintf(stderr, "Unable to allocate memory.\n");
 | |
| 	fprintf(stderr, "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) {
 | |
| 	fprintf(stderr, "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)) {
 | |
| 	fprintf(stderr, "Unable to create named pipe %s.\n", NPIPE);
 | |
| 	return 1;
 | |
|   } else
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void 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()
 | |
| {
 | |
|   ssize_t n;
 | |
|   int written;
 | |
|   char header[80];
 | |
| 
 | |
|   printf("Writing to %s ...", outfile);
 | |
| 
 | |
|   /* Write header. */
 | |
|   if (SPROF)
 | |
| 	sprintf(header, "stat\n%u %u %u\n",	sizeof(struct sprof_info_s),
 | |
| 						sizeof(struct sprof_sample),
 | |
| 					  	sizeof(struct sprof_proc));
 | |
|   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) {
 | |
| 	fprintf(stderr, "Error writing to outfile %s.\n", outfile);
 | |
| 	return 1;
 | |
|   }
 | |
| 
 | |
|   /* for sprofile, raw data will do; cprofile is handled by a script that needs
 | |
|    * some preprocessing to be done by us
 | |
|    */
 | |
|   if (SPROF) {
 | |
| 	written = write_outfile_sprof();
 | |
|   } else {
 | |
| 	written = write_outfile_cprof();
 | |
|   }
 | |
|   if (written < 0) return -1;
 | |
| 
 | |
|   printf(" header %d bytes, data %d bytes.\n", strlen(header), written);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| int write_outfile_cprof()
 | |
| {
 | |
|   int towrite, written = 0;
 | |
|   struct sprof_sample *sample;
 | |
| 
 | |
|   /* 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) {
 | |
| 		  fprintf(stderr, "Error writing to outfile %s.\n", outfile);
 | |
| 		  return -1;
 | |
| 	  }
 | |
| 	  towrite -= sizeof(struct sprof_sample);
 | |
| 	  sample++;
 | |
| 
 | |
| 	  written += 12;
 | |
|   }
 | |
| 
 | |
|   return written;
 | |
| }
 | |
| 
 | |
| int write_outfile_sprof()
 | |
| {
 | |
|   int towrite;
 | |
|   ssize_t written, written_total = 0;
 | |
| 
 | |
|   /* write profiling totals */
 | |
|   written = write(outfile_fd, &sprof_info, sizeof(sprof_info));
 | |
|   if (written != sizeof(sprof_info)) goto error;
 | |
|   written_total += written;
 | |
| 
 | |
|   /* write raw samples */
 | |
|   towrite = mem_used == -1 ? mem_size : mem_used;
 | |
|   written = write(outfile_fd, mem_ptr, towrite);
 | |
|   if (written != towrite) goto error;
 | |
|   written_total += written;
 | |
| 
 | |
|   return written_total;
 | |
| 
 | |
| error:
 | |
|   fprintf(stderr, "Error writing to outfile %s.\n", outfile);
 | |
|   return -1;
 | |
| }
 | 
