/*
 *  kerneltop.c - shows kernel function usage in a style like 'top'
 *    derived from readprofile.c
 *
 *  Copyright (C) 1994,1996 Alessandro Rubini (rubini@ipvvis.unipv.it)
 *  Copyright (C) 2002,2004 Randy Dunlap <rddunlap@osdl.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * 2002-05-19 Randy Dunlap <rddunlap@osdl.org>
 * - Modify for 'kerneltop'
 * - TBD:
 * . make program know how many lines are on the console (instead
 *   of being told);
 * . add system/user/idle time per reporting period;
 * . add support for kernel modules; ... NO;
 * . add syscall counters per reporting period;
 * . use poll() for stdin/waiting;
 *
 * 2002-05-22: Version 0.5:
 * . first public release
 * 2002-05-24: Version 0.6:
 * . add conditional keyboard inputs for l(ines), s(econds), t(icks),
 *   u(nsort), h(elp) or ?(help), q(uit)
 * 2004-05-23: Version 0.7:
 * . make a difference in prof_lines and console_lines; they were
 *   the same, which caused some heading lines to scroll off the
 *   top of the console and make general garbage of the headings;
 * . reduce the fixed heading info by 2 lines (no version, no dash line);
 * 2004-05-24: Version 0.8:
 * . don't reset /proc/profile; just do incremental/interval diffs of it;
 *   (from Bill Irwin <wli@holomorphy.com>);
 * . move total_ticks to a heading line, don't waste an entire line for it;
 *   (from Stuart Young <cef-lkml@optusnet.com.au>);
 * . put "address  function ....." heading line in reverse video;
 * . add get_unames() to save uname info;
 * . if mapfile name is not specified (-m) and /boot/System.map is not
 *   found, try to open /boot/System.map-`uname -r`;
 * 2004-05-27: Version 0.9:
 * . correct getopt's optstring (remove 'n'; add 'r');
 *   noticed by Stuart Young;
 * . if a mapfile name is not specified, try to open in this order:
 *   /boot/System.map-`uname -r`, /boot/System.map, and
 *   /lib/modules/`uname -r`/System.map;
 * . add man page (from Stuart Young);
 * . remove ioctl's from keyboard I/O routine; not needed/used;
 *   keyboard handling still needs some work;
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <fcntl.h>
#include <pwd.h>
#include <time.h>
#include <ctype.h>
#ifdef NLS
#include "nls.h"
#else
/////following 2 #defines from nls.h:
#define _(Text) (Text)
#define N_(Text) (Text)
#endif
#include "vidsupport.h"

#define VERSION "0.9"

// video output rows/lines & columns
#define ROW_TITLE1	1
#define ROW_SAMPLING_RANGE	2
#define ROW_HEADINGS	3
#define NUM_HEADINGS_LINES	3
#define ROW_DATA	4	// first row of function/usage data
#define ROW_MESSAGE	1

#define S_LEN		1024	// fix debian bug #607309
#define OUTPUT_SIZE	512

#define DEF_NUMLINES	20		/* lines */
#define MAX_NUMLINES	100		/* lines */
#define DEF_THRESHOLD	1		/* ticks */
#define BAD_INPUT	(-42)

#define SHOWMESSAGE(x) do { 			\
		vid_curpos (ROW_MESSAGE, 1);	\
		vid_clear_curtoeol();		\
		printf x;			\
		vid_putnow();			\
		sleep(2);			\
	} while (0)

// symbols and addresses from System.map file
struct text_symbol {
	unsigned long	textadr;
	char		textname[1];	// this field size is *dynamic*
};

// table counting kernel function's ticks and index into <textsym> table
struct freq_table {
	int		ticks;
	int		textindex;
};

static char *prgname;

/* These are the defaults */
static char defaultmap[] = "/boot/System.map";
static char defaultpro[] = "/proc/profile";
static char optstring[] = "m:p:rs:l:t:uV";

// some command-line options:
int console_lines = DEF_NUMLINES;
int prof_lines = DEF_NUMLINES - NUM_HEADINGS_LINES;
int sleep_seconds = 1;
int threshold = DEF_THRESHOLD;
int optUnsorted = 0;

char	uname_all [OUTPUT_SIZE], uname_release [OUTPUT_SIZE];

int popenMap;   /* flag to tell if popen() has been used */
FILE *mapf;
int *freqs;		// frequency table (histogram) for each text_symbol
struct freq_table freqtable [MAX_NUMLINES];
struct text_symbol * textsymroot; // 0th element of <text_symbol> array
struct text_symbol * textsym;	  // anywhere in <text_symbol> array
struct text_symbol * textsymlast; // last element of <text_symbol> array
unsigned long adr0 = 0; // this is begin_text
unsigned long adrz = 0; // this is end_text
int max_fn_len = 0;	// size of the "dynamic" text_symbol.textname field
int text_lines = 0;	// number of T/t lines in mapfile; number of entries in textsym table
int text_entry_size;	// size of one <text_symbol>
int lookup_last;	// last used index in lookup() function

/* round up number <n> by <size>, where <size> is a power of 2 */
static inline long round_up (long n, long size) {
	return (n + size - 1) & ~(size - 1);
}

/*
 * System.map symbols (from 'nm'):
 * A Symbol value is absolute
 * B Symbol is in uninitialized BSS data section
 * C Symbol is common, uninitialized data
 * D Symbol is in initialized data section
 * G Symbol is in initialized data section for small objects
 * I Symbol is an indirect reference to another symbol
 * N Symbol is a debugging symbol
 * R Symbol is in a read-only data section
 * S Symbol is in an uninitialized data section for small objects
 * T Symbol is in the text (code) section
 * U Symbol is undefined
 * V Symbol is a weak object
 * W Symbol is a weak symbol but not tagged as a weak object symbol
 * - Symbol is a stabs symbol in an a.out object file
 * ? Symbol type is unknown or object file format specific
 *
 * What kerneltop wants/needs is all of the 'T' and 't' symbols that
 * are between "<address> T _stext" and "<address> A _etext".
 */

static void
usage(void) {
	fprintf(stderr,
		  _("%s: usage: \"%s [options]\n"
		  "\t -m <mapfile>  (default = \"%s\"[-`uname -r`])\n"
		  "\t -p <pro-file> (default = \"%s\")\n"
		  "\t -l <lines>    set number of lines to print (def. = %d; max = %d)\n"
		  "\t -s <seconds>  set sleep time in seconds (def. = 1)\n"
		  "\t -t <ticks>    set threshold of number of ticks to print (def. = 1)\n"
		  "\t                 a function must have this many ticks to be printed\n"
		  "\t -u            print unsorted (skip the sort)\n"
		  "\t -V            print version and exit\n"
	          ), prgname, prgname, defaultmap, defaultpro,
		  DEF_NUMLINES, MAX_NUMLINES);
#ifdef OPT_REVERSE
		  "\t -r            reverse byte order in profile file\n"
#endif
	exit (1);
}

static void *
xmalloc (size_t size) {
	void *t;

	if (size == 0)
		return NULL;

	t = malloc (size);
	if (t == NULL) {
		fprintf (stderr, _("[out of memory]"));
		exit (1);
	}

	return t;
}

static FILE *
myopen (const char *name, char *mode, int *flag) {
	int len = strlen (name);

	if (!strcmp (name + len - 3, ".gz")) {
		FILE *res;
		char *cmdline = xmalloc(len + 6);
		sprintf(cmdline, "zcat %s", name);
		res = popen (cmdline, mode);
		free (cmdline);
		*flag = 1;
		return res;
	}
	*flag = 0;
	return fopen (name, mode);
}

/*
 * Get a string from the user; the base of getint(), et al.
 * This really ought to handle long input lines and errors better.
 * NB: The pointer returned is a statically allocated buffer,
 * so beware of how it's used.
 */
char *getstr (void)
{
	static char line[BUFSIZ];	/* BUFSIZ from <stdio.h>; arbitrary */
	int i = 0;

	/* Must make sure that buffered IO doesn't kill us. */
	fflush(stdout);
	fflush(stdin);		/* Not POSIX but ok */

	do {
		read (STDIN_FILENO, &line[i], 1);
	} while (line[i++] != '\n' && i < sizeof(line));
	line[--i] = 0;

	return (line);
}

/*
 * Get an integer from the user.
 * Display an error message and return BAD_INPUT if it's invalid;
 * else return the number.
 */
int getint (void)
{
	char *line;
	int i;
	int r;

	line = getstr();

	for (i = 0; line[i]; i++) {
		if (!isdigit(line[i]) && line[i] != '-') {
			SHOWMESSAGE(("That's not a number!"));
			return (BAD_INPUT);
		}
	}

	/* An empty line is a legal error (hah!). */
	if (!line[0])
		return (BAD_INPUT);

	sscanf(line, "%d", &r);
	return (r);
}

static void get_unames (char *uname_all, char *uname_release)
{
	FILE *pin;

	pin = popen ("uname -a", "r");
	if (pin == NULL) {
		fprintf (stderr, "can't exec 'uname': error (%d) = %s\n",
			errno, strerror (errno));
		exit (1);
	}
	memset (uname_all, 0, sizeof(*uname_all));
	fread (uname_all, OUTPUT_SIZE - 1, 1, pin);
	pclose (pin);

	pin = popen ("uname -r", "r");
	if (pin == NULL) {
		fprintf (stderr, "can't exec 'uname': error (%d) = %s\n",
			errno, strerror (errno));
		exit (1);
	}
	memset (uname_release, 0, sizeof(*uname_release));
	fread (uname_release, OUTPUT_SIZE - 1, 1, pin);
	pclose (pin);
	if (uname_release [strlen (uname_release) - 1] == '\n')
		uname_release [strlen (uname_release) - 1] = '\0';
}

/*
 * heading looks like (3 lines):
<uname -a>
Sampling step: n | Address range: s_text_addr - e_text_addr
address  function ...... date/time ...... ticks (total_ticks)
 */
static void heading (void)
{
#ifdef USER_HOST_CWD
	char reply [OUTPUT_SIZE];
	char *usr = NULL;
	struct passwd *pwd;
	int err;
#endif

	vid_clear_all();
	// print uname -a
	vid_curpos (ROW_TITLE1, 1);
	printf ("%s", uname_all);	// has trailing \n

#ifdef USER_HOST_CWD
	// print username/hostname/cwd
	pwd = getpwuid (geteuid ());
	if (!pwd)
		usr = getenv ("LOGNAME");
	if (!usr)
		usr = getenv ("USER");
	printf ("user: %s ", pwd ? pwd->pw_name : usr ? usr : "(?)");

	err = gethostname (reply, sizeof(reply));
	if (err)
		fprintf (stderr, "gethostname error (%d) = %s\n",
				errno, strerror (errno));
	else
		printf ("| host: %s\n", reply);

	if (getcwd (reply, sizeof(reply)))
		printf ("cwd: %s\n", reply);
	else
		fprintf (stderr, "getcwd error (%d) = %s\n",
			errno, strerror (errno));
#endif
} // end heading

// helper:  show run-time help:
// first clear display, print help list;
// wait for keyboard input;
// clear display;
// return
//
void helper (void)
{
	vid_clear_all();

	printf (_("%s Version %s\n"), prgname, VERSION);
	printf ("h or ? : this help text\n");
	printf ("l #    : set number of console lines to print: %d\n",
			console_lines);
	printf ("s #    : set number of seconds between updates: %d\n",
			sleep_seconds);
	printf ("t #    : set threshold for number of ticks required to be printed: %d\n",
			threshold);
	printf ("q      : quit\n");
	printf ("u      : print profile (un)sorted: %d\n", optUnsorted);

	printf("\nPress any key to continue");
	vid_putnow();

	///sleep (1);
	///tcsetattr(0, TCSANOW, &Rawtty);
	(void) getchar();
	vid_clear_all();
	heading();
} // end helper

/// insert keypressed.c here....
/*
 * keypressed.c
 * by Jan-Benedict Glaw <jbglaw@lug-owl.de>
 * from the gcc@gnu.org mailing list, 2002-Sep-21.
 *
 * Actually just convers terminal input from line-oriented mode
 * to character-oriented mode.
 */

// Build with:  gcc -O2 -c keypressed.c  {for linkable keypressed.o file}
// Build with:  gcc -O2 -o keypressed -DTEST keypressed.c  {for test program}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
///#include <sys/ioctl.h>
#include <linux/kd.h>

int keypressed (int keyboard_fd) {
	char		one_key;
	struct termios	termio_backup;
	struct termios	termio_new;
	ssize_t		actual;
#ifdef RAW_MODE
	int		old_mode;
	int		meta_mode;
	int		temp = K_RAW;
#endif

	tcgetattr(keyboard_fd, &termio_backup);
	memcpy(&termio_new, &termio_backup, sizeof(struct termios));
///	ioctl(keyboard_fd, KDGKBMODE, &old_mode);
///	ioctl(keyboard_fd, KDGKBMETA, &meta_mode);

	termio_new.c_lflag      &= ~(ICANON|ECHO|ISIG);
	termio_new.c_iflag      = 0;
	///termio_new.c_cc[VMIN]   = 1;
	///termio_new.c_cc[VTIME]  = 100;
	termio_new.c_cc[VMIN]   = 0;
	termio_new.c_cc[VTIME]  = 0;
	tcsetattr(keyboard_fd, TCSANOW, &termio_new);
///	ioctl(keyboard_fd, KDSKBMODE, temp);
///	ioctl(keyboard_fd, KDSKBMETA, temp);
	///tcflush(keyboard_fd, TCIOFLUSH);

	actual = read (keyboard_fd, &one_key, 1);
	// actual should be 0 or 1 now.
#ifdef UNGET
	// If actual == 1, the key is in <one_key>, so put it back.
	if (actual == 1)
		ungetc (one_key, stdin);
#endif

///	ioctl(keyboard_fd, KDSKBMODE, old_mode);
///	ioctl(keyboard_fd, KDSKBMETA, meta_mode);
	tcsetattr(keyboard_fd, TCSANOW, &termio_backup);
	///tcflush(keyboard_fd, TCIOFLUSH);

	///fprintf (stderr, "\nactual read count = %d, key = 0x%x\n",
	///		actual, one_key);
#ifdef UNGET
	return actual;
#else
	return actual ? (unsigned)one_key : 0;
#endif
} // end keypressed
// end keypressed.c

/*
 * Process keyboard input during the main loop
 * Returns 0 to continue (for any key except 'q';
 * returns -1 to quit
 *
 * - conditional keyboard inputs for l(ines), s(econds), t(icks),
 *   u(nsort), h(elp) or ?(help), q(uit)
 */
int do_key(char c)
{
    int numinput;

    if (c == 'q')
	return -1;
    if (c == ' ')
        return 0;
    if (c == 'h' || c == '?') {
	    helper();
	    return 0;
    }

    /*
     * Switch the terminal to normal mode.  (Will the original
     * attributes always be normal?  Does it matter?  I suppose the
     * shell will be set up the way the user wants it.)
     */
    ///tcsetattr(0, TCSANOW, &Savetty);

    /*
     * Handle the rest of the commands.
     */
    switch (c) {
    case 'l':		// set <console_lines>
	printf("Number of console lines to print: ");
	numinput = getint();
	if (numinput != BAD_INPUT)
	    if (numinput > 0 && numinput <= MAX_NUMLINES) {
		  console_lines = numinput;
		  prof_lines = console_lines - NUM_HEADINGS_LINES;
	    }
	break;

    case 's':		// set sleep_seconds
	printf("Seconds between updates: ");
	numinput = getint();
	if (numinput != BAD_INPUT)
	    if (numinput > 0)
		  sleep_seconds = numinput;
	break;

    case 't':
	printf("Threshold of ticks to display (0 for unlimited): ");
	numinput = getint();
	if (numinput != BAD_INPUT)
	    if (numinput > 0)
		threshold = numinput;
	break;

    case 'u':
	optUnsorted = !optUnsorted;
	break;
    } // end switch

    /*
     * Return to raw mode.
     */
    ///tcsetattr(0, TCSANOW, &Rawtty);

    vid_curpos (ROW_TITLE1, 0);
    vid_clear_line();
    printf (_("%s Version %s\n"), prgname, VERSION);
    return 0;
} // end do_key


/*
 * allocates <buf> for the profile data;
 * returns buffer length;
 */
static unsigned long profile_read (const char *proFile, unsigned int **buf)
{
	int proFd;
	unsigned long len;

	/*
	 * Use an fd for the profiling buffer, to skip stdio overhead
	 */
	if ( ((proFd = open (proFile, O_RDONLY)) < 0)
	     || ((int)(len = lseek (proFd, 0, SEEK_END)) < 0)
	     || (lseek (proFd, 0, SEEK_SET) < 0) ) {
		fprintf (stderr, "%s: %s: %s\n",
				prgname, proFile, strerror (errno));
		fprintf (stderr, "enable kernel profiling [profile=N]\n");
		exit (1);
	}

	if ( !(*buf = malloc (len)) ) {
		fprintf (stderr, "%s: malloc(%ld): %s\n",
				prgname, len, strerror (errno));
		exit (1);
	}

	if (read (proFd, *buf, len) != len) {
		fprintf (stderr, "%s: %s: %s\n",
				prgname, proFile, strerror (errno));
		exit (1);
	}
	close (proFd);
	return (len);
} // end profile_read

/*
 * read mapfile and count the T/t lines between begin_text & end_text;
 * also find the max. function name length while reading;
 * also verify ascending address order;
 * then malloc for an "array" of text_entries * text_entry_size;
 * re-read mapfile and save the T/t lines (already sorted);
 */
static void mapfile_read (const char *mapFile, int opt_mapfile)
{
	int maplineno = 0;
	char mapline [S_LEN];
	unsigned long seek_adr = 0;
	unsigned long fn_adr, prev_adr = 0;
	char fn_name [S_LEN];
	char mode [8];
	int fn_len;
	char begin_text[] = "_stext";	// want all T/t between begin/end text
	char end_text[] = "_etext";	// this one is Absolute.

	// try specified/default name first.
	if ((mapf = myopen (mapFile, "r", &popenMap)))
		goto open_ok;
	if (opt_mapfile)	// then don't try the defaults;
		goto open_err;

	// open order for default mapfile name is:
	// (i.e., for filename not specified):
	// /boot/System.map-`uname -r`, /boot/System.map,
	// /lib/modules/`uname -r`/System.map (& that's all);

	// try default + uname_release;
	strcpy (fn_name, defaultmap);
	strcat (fn_name, "-");
	strcat (fn_name, uname_release);
	if ((mapf = myopen (fn_name, "r", &popenMap)))
		goto open_ok;
	// try default without uname_release;
	if ((mapf = myopen (defaultmap, "r", &popenMap)))
		goto open_ok;
	// try /lib/modules/uname_release/System.map;
	strcpy (fn_name, "/lib/modules/");
	strcat (fn_name, uname_release);
	strcat (fn_name, "/System.map");
	if ((mapf = myopen (fn_name, "r", &popenMap)))
		goto open_ok;

open_err:
	fprintf (stderr, "%s: %s: %s\n",
			prgname, mapFile, strerror (errno));
	exit (1);

open_ok:
	// first find <begin_text>
	while (fgets (mapline, S_LEN, mapf)) {
		maplineno++;
		if (sscanf (mapline, "%lx %s %s", &fn_adr, mode, fn_name) != 3) {
			fprintf (stderr, _("%s: %s(%i): wrong map line\n"),
				prgname, mapFile, maplineno);
			exit (1);
		}

		if (*mode == 'T' &&
		    !strcmp (fn_name, begin_text)) { // only ELF works like this
			adr0 = fn_adr;
			seek_adr = ftell (mapf); // save filepos of line after <begin_text>
			break;
		}
	}

	if (!adr0) {
		fprintf (stderr, _("%s: can't find '%s' in %s\n"),
			prgname, begin_text, mapFile);
		exit (1);
	}

	// count T/t lines between begin_text & end_text;
	// find max_fn_len;
	// verify addresses are non-descending
	while (fgets (mapline, S_LEN, mapf)) {
		maplineno++;
		if (sscanf (mapline, "%lx %s %s", &fn_adr, mode, fn_name) != 3) {
			fprintf (stderr, _("%s: %s(%i): wrong map line\n"),
				prgname, mapFile, maplineno);
			exit (1);
		}

		if (*mode == 'A' &&
		    !strcmp (fn_name, end_text)) {
			adrz = fn_adr;
			text_lines++;
			break;
		}
		if (fn_adr < prev_adr) {
			fprintf (stderr, _("%s: %s(%i): descending address order in map line\n"),
				prgname, mapFile, maplineno);
			exit (1);
		}
		if (*mode != 'T' && *mode != 't')
			continue;
		text_lines++;
		fn_len = strlen (fn_name);
		if (fn_len > max_fn_len)
			max_fn_len = fn_len;
		prev_adr = fn_adr;
	}
	text_lines++;	// count the "T _stext" (begin_text) line too

	// make max_fn_len a multiple of 4 (round up)
	max_fn_len = round_up (max_fn_len, 4);
	text_entry_size = sizeof(unsigned long) + max_fn_len;
	// allocate an "array" of <text_lines> entries of <text_symbol>s.
	textsymroot = (struct text_symbol *) malloc (text_lines * text_entry_size);
	if (!textsymroot) {
		fprintf (stderr, _("%s: cannot allocate %d bytes \n"),
			prgname, text_lines * text_entry_size);
		exit (1);
	}
	textsym = textsymroot;

	// put <begin_text> in the textsym table
	textsym->textadr = adr0;
	strcpy (textsym->textname, begin_text);
	textsym = (struct text_symbol *) ((char *)textsym + text_entry_size);

	// re-read mapfile and save the T/t lines and the end_text line
	fseek (mapf, seek_adr, SEEK_SET);	// back to line after <begin_text>
	while (fgets (mapline, S_LEN, mapf)) {
		if (sscanf (mapline, "%lx %s %s", &fn_adr, mode, fn_name) != 3) {
			fprintf (stderr, _("%s: %s(%i): wrong map line\n"),
				prgname, mapFile, maplineno);
			exit (1);
		}
		if (*mode == 'A' &&
		    !strcmp (fn_name, end_text))
			break;
		if (*mode != 'T' && *mode != 't')
			continue;
		textsym->textadr = fn_adr;
		strcpy (textsym->textname, fn_name);
		textsym = (struct text_symbol *) ((char *)textsym + text_entry_size);
	}
	textsym->textadr = adrz;
	strcpy (textsym->textname, end_text);
	textsym = (struct text_symbol *) ((char *)textsym + text_entry_size);
	textsymlast = textsym;
} // end mapfile_read

/*
 * returns index into the textsym table for this <adr>
 * or -1 if not found.
 * Since textsym table is in non-descending order by address,
 * an exhaustive search isn't needed.  Not even a binary search.
 * Just search from <lookup_last> forward until out of range.
 * Number of elements in textsym table is <text_lines>.
 */
static int lookup (unsigned long adr)
{
	textsym = (struct text_symbol *) ((char *)textsymroot + text_entry_size * lookup_last);
	while (textsym->textadr < adr) {
		if (textsym->textadr == adrz) break;
		textsym = (struct text_symbol *) ((char *)textsym + text_entry_size);
		lookup_last++;
	}
	if (textsym->textadr == adr)
		return lookup_last;
	if (textsym->textadr > adr)
		return (lookup_last - 1);
	return -1;
} // end lookup

// copy the >= threshold freqs[] entries (up to MAX_NUMLINES of them),
// adding tableindex (textindex) to the freq_table entries.
// returns number of qualifying entries in the output freq_table.
static int copy_freqs(int *freqs, int threshold, struct freq_table sortfreqs[])
{
	int ix, ticks, saved = 0;

	for (ix = 0; ix < text_lines; ix++) {
		ticks = freqs [ix];
		///textsym = (struct text_symbol *) ((char *)textsymroot + text_entry_size * ix);
		if (ticks >= threshold) {
			sortfreqs[saved].ticks = ticks;
			sortfreqs[saved].textindex = ix;
			saved++;
			if (saved >= MAX_NUMLINES)	// silently drop any others
				break;
		}
	}
	return saved;
} // end copy_freqs

///static void insertionSort(int numbers[], int array_size)
// sort on sortfreqs[].ticks in _descending_ order (use < instead of >).
static void insertionSort(struct freq_table sortfreqs[], int array_size)
{
	int i, j;
	int xticks, xtextindex;

	for (i = 1; i < array_size; i++) {
		xticks = sortfreqs[i].ticks;
		xtextindex = sortfreqs[i].textindex;
		j = i;

		while ((j > 0) && (sortfreqs[j-1].ticks < xticks)) {
			sortfreqs[j].ticks = sortfreqs[j-1].ticks;
			sortfreqs[j].textindex = sortfreqs[j-1].textindex;
			j = j - 1;
		}
		sortfreqs[j].ticks = xticks;
		sortfreqs[j].textindex = xtextindex;
	}
}

// sort the >= threshold freqs[] entries (up to MAX_NUMLINES of them),
// adding tableindex (textindex) to the freq_table entries.
// returns number of qualifying entries in the output freq_table.
// This uses the insertion sort algorithm.
// It would be faster to use the shell sort or one of
// merge, heap, or quick sorts.
static void sort_freqs(int valid, struct freq_table sortfreqs[])
{
	insertionSort (sortfreqs, valid);
} // end sort_freqs

int
main (int argc, char **argv) {
	char *mapFile, *proFile;
	unsigned long len = 0;
	unsigned int step;
	unsigned int *lastbuf, *buf = NULL;
	int opt;
	int optReverse = 0, optProfile = 0, optMapfile = 0;
	time_t timer;
	struct tm *dttm;
	int total_ticks = 0, ticks;
	int ix, mx, fnx, slpx;
	unsigned long adr;
	int valid;
	char ch;

#ifdef NLS
	setlocale (LC_ALL, "");
	bindtextdomain (PACKAGE, LOCALEDIR);
	textdomain (PACKAGE);
#endif

	prgname = argv[0];
	proFile = defaultpro;
	mapFile = defaultmap;
	get_unames (uname_all, uname_release);

	while ((opt = getopt (argc, argv, optstring)) != -1) {
		switch (opt) {
		case 'm': mapFile = optarg;		// def. /boot/System.map
			  optMapfile++;			// not using default
			  break;
		case 'p': proFile = optarg;		// def. /proc/profile
			  optProfile++;			// not using default
			  break;
		case 'l': console_lines = strtol (optarg, NULL, 0);
			  if (console_lines <= 0 || console_lines > MAX_NUMLINES)
				  console_lines = DEF_NUMLINES;
			  break;
		case 's': sleep_seconds = strtol (optarg, NULL, 0);
			  if (sleep_seconds <= 0)
				  sleep_seconds = 1;
			  break;
		case 'r': optReverse++; break;
		case 't': threshold = strtol (optarg, NULL, 0);
			  if (threshold < 1)
				  threshold = 1;
			  break;
		case 'u': optUnsorted++; break;
		case 'V': printf (_("%s Version %s\n"), prgname, VERSION);
			exit (0);
		default: usage(); break;
		}
	}

	/* general flow:
	 * read the mapfile 1 time;
	 * print headings;
	 * loop:
	 ** read profile; allocates buf;
	 ** reset profile;
	 ** print top prof_lines lines of it;
	 ** free buf;
	 ** sleep for sleep_seconds and check for keyboard input;
	 * endloop;
	 */

	mapfile_read (mapFile, optMapfile);
	popenMap ? pclose (mapf) : fclose (mapf);

	// allocate & clear freqs table
	freqs = (int *) xmalloc (sizeof(int) * (text_lines + 1));
	memset (freqs, 0, sizeof(int) * (text_lines + 1));

	heading ();

    while (1) {		// get profile data, put it into symbol buckets, print
	lastbuf = buf;
	len = profile_read (proFile, &buf); // allocates buf & returns its len
	step = buf[0];
	vid_curpos (ROW_SAMPLING_RANGE, 1);
	printf (_("Sampling_step: %i | Address range: 0x%lx - 0x%lx\n"),
		step, adr0, adrz);

	time (&timer);
	dttm = localtime (&timer);
	vid_curpos (ROW_HEADINGS, 1);
	vid_attr (ATTR_REVERSE_ON);
	printf (_("address  function ...... %d-%02d-%02d/%02d:%02d:%02d ...... ticks"),
		dttm->tm_year + 1900, dttm->tm_mon + 1, dttm->tm_mday,
		dttm->tm_hour, dttm->tm_min, dttm->tm_sec);

#ifdef OPT_REVERSE
	if (optReverse) {	// reverse the byte order in the profile
		int entries = len / sizeof(*buf);
		int i;
		unsigned int *p;

		for (p = buf; p < buf + entries; p++)
			for (i = 0; i < sizeof(*buf) / 2; i++) {
				unsigned char *b = (unsigned char *) p;
				unsigned char tmp;

				tmp = b [i];
				b [i] = b [sizeof(*buf) - i - 1];
				b [sizeof(*buf) - i - 1] = tmp;
			}
	} // end !optReverse
#endif // OPT_REVERSE

	mx = len / sizeof(int) - 1;	// discount <step> part of buf.
	// collect frequency histogram of profile buf.
	for (ix = 0, lookup_last = 0; ix < mx; ix++) {
		adr = adr0 + step * ix;
		ticks = buf [ix + 1] - (lastbuf ? lastbuf [ix + 1] : 0);
		total_ticks += ticks;
		fnx = lookup (adr);
		if (fnx >= 0)
			freqs [fnx] += ticks;
	}
	// Print total on end of previous string
	vid_clear_curtoeol();
	printf (" (%6i)\n", total_ticks);
	vid_attr (ATTR_REVERSE_OFF);

	/*
	 * Optionally sort the top <prof_lines> entries.
	 * Print the top entries.
	 */

	// copy or sort the non-0 freqs[] entries (up to MAX_NUMLINES of them)
	valid = copy_freqs(freqs, threshold, freqtable);
	if (!optUnsorted)
		sort_freqs(valid, freqtable);

	// print the top <prof_lines> qualifying elements of <freqtable>
	// The elements of <freqtable> are already >= threshold.
	for (ix = 0; ix < valid; ix++) {
		if (ix >= prof_lines) break;
		ticks = freqtable[ix].ticks;
		textsym = (struct text_symbol *) ((char *)textsymroot + text_entry_size * freqtable[ix].textindex);
		vid_curpos (ROW_DATA + ix, 1);
		vid_clear_curtoend();
		printf ("%08lx %-41s %6i\n",
			textsym->textadr, textsym->textname, ticks);
	}

	total_ticks = 0;
	// clear freqs for next pass
	memset (freqs, 0, sizeof(int) * (text_lines + 1));
	free (lastbuf);	// before next profile_read call

	// check for stdin (keyboard) input 1x per second
	// while sleeping for <sleep_seconds>
	// (so 1-second resolution)
	for (slpx = 0; slpx < sleep_seconds; slpx++) {
		sleep (1);
		if ((ch = keypressed (STDIN_FILENO))) {
			if (do_key (ch) < 0) goto fini;
		}
	}

    } // end while 1
    free (buf);

fini:
	printf("\n");
	free (freqs);
	free (textsymroot);
	exit (0);
} // end main

// end kerneltop.c
