/* * 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 * * 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 * - Modify for 'kerneltop' * - TBD: * . maybe add read_all_profile_and_clear() in kernel [i.e., atomic] * . 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 ); * . move total_ticks to a heading line, don't waste an entire line for it; * (from 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`; */ #include #include #include #include #include #include #include #include #include #include #include #include #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.8" // 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 128 #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 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:np:s: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 array struct text_symbol * textsym; // anywhere in array struct text_symbol * textsymlast; // last element of 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 int lookup_last; // last used index in lookup() function /* round up number by , where 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 "
T _stext" and "
A _etext". */ static void usage(void) { fprintf(stderr, _("%s: usage: \"%s [options]\n" "\t -m (default = \"%s\")\n" "\t -p (default = \"%s\")\n" "\t -l set number of lines to print (def. = %d; max = %d)\n" "\t -s set sleep time in seconds (def. = 1)\n" "\t -t 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 ; 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): 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 * 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 #include #include #include #include #include #include int keypressed (int keyboard_fd) { char one_key; struct termios termio_backup; struct termios termio_new; int old_mode; int meta_mode; int temp = K_RAW; ssize_t actual; 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 , 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 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 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. if (!(mapf = myopen (mapFile, "r", &popenMap))) { // if !opt_mapfile, try default + uname_release; if (!opt_mapfile) { strcpy (fn_name, defaultmap); strcat (fn_name, "-"); strcat (fn_name, uname_release); if ((mapf = myopen (fn_name, "r", &popenMap))) goto file_ok; } fprintf (stderr, "%s: %s: %s\n", prgname, mapFile, strerror (errno)); exit (1); } file_ok: // first find 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 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 entries of 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 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 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 * 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 forward until out of range. * Number of elements in textsym table is . */ 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; int first = 1; 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 'n': 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); first = 0; 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 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 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 qualifying elements of // The elements of 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 // (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