Date: Wed, 26 Nov 2008 21:44:58 +0000 (UTC) From: Attilio Rao <attilio@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r185347 - in head/usr.sbin: . pmcannotate Message-ID: <200811262144.mAQLiwLG067160@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: attilio Date: Wed Nov 26 21:44:57 2008 New Revision: 185347 URL: http://svn.freebsd.org/changeset/base/185347 Log: Import an initial revision of the pmcannotate tool. For further explanations please check this e-mail on freebsd-arch@: http://lists.freebsd.org/pipermail/freebsd-arch/2008-November/008698.html Tested by: gnn Sponsored by: Nokia Added: head/usr.sbin/pmcannotate/ head/usr.sbin/pmcannotate/Makefile (contents, props changed) head/usr.sbin/pmcannotate/pmcannotate.8 (contents, props changed) head/usr.sbin/pmcannotate/pmcannotate.c (contents, props changed) Modified: head/usr.sbin/Makefile Modified: head/usr.sbin/Makefile ============================================================================== --- head/usr.sbin/Makefile Wed Nov 26 21:38:43 2008 (r185346) +++ head/usr.sbin/Makefile Wed Nov 26 21:44:57 2008 (r185347) @@ -119,6 +119,7 @@ SUBDIR= ${_ac} \ pciconf \ periodic \ ${_pkg_install} \ + ${_pmcannotate} \ ${_pmccontrol} \ ${_pmcstat} \ ${_pnpinfo} \ @@ -348,6 +349,7 @@ _pkg_install= pkg_install # XXX MK_TOOLCHAIN? .if ${MK_PMC} != "no" +_pmcannotate= pmcannotate _pmccontrol= pmccontrol _pmcstat= pmcstat .endif Added: head/usr.sbin/pmcannotate/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/pmcannotate/Makefile Wed Nov 26 21:44:57 2008 (r185347) @@ -0,0 +1,12 @@ +# +# $FreeBSD$ +# + +PROG= pmcannotate +MAN= pmcannotate.8 + +WARNS?= 6 + +SRCS= pmcannotate.c + +.include <bsd.prog.mk> Added: head/usr.sbin/pmcannotate/pmcannotate.8 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/pmcannotate/pmcannotate.8 Wed Nov 26 21:44:57 2008 (r185347) @@ -0,0 +1,108 @@ +.\" Copyright (c) 2008 Nokia Corporation +.\" All rights reserved. +.\" +.\" This software was developed by Attilio Rao for the IPSO project under +.\" contract to Nokia Corporation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" This software is provided by the authors ``as is'' and +.\" any express or implied warranties, including, but not limited to, the +.\" implied warranties of merchantability and fitness for a particular purpose +.\" are disclaimed. in no event shall the authors be liable +.\" for any direct, indirect, incidental, special, exemplary, or consequential +.\" damages (including, but not limited to, procurement of substitute goods +.\" or services; loss of use, data, or profits; or business interruption) +.\" however caused and on any theory of liability, whether in contract, strict +.\" liability, or tort (including negligence or otherwise) arising in any way +.\" out of the use of this software, even if advised of the possibility of +.\" such damage. +.\" +.\" $FreeBSD$ +.\" +.Dd November 20, 2008 +.Os +.Dt PMCANNOTATE 8 +.Sh NAME +.Nm pmcannotate +.Nd "sources printout with inlined profiling" +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl h +.Op Fl k Ar pathname +.Op Fl l Ar level +.Ar pmcout.out binaryobj +.Sh DESCRIPTION +The +.Nm +utility can produce both C sources or assembly sources of a program with +a line-by-line based profiling. +The profiling informations are retrieved through a +.Xr pmcstat 8 +raw output while the program operations are retrieved through the +.Xr objdump 1 +tool. +.Pp +When calling +.Nm +the raw output is passed through the +.Ar pmcout.out +argument, while the program is passed through the +.Ar binaryobj +argument. +.Pp +As long as +.Nm +relies on +.Xr objdump 1 +and +.Xr pmcstat 8 +to work, it will fail if one of them is not available. +.Sh OPTIONS +The following options are available: +.Bl -tag -width indent +.It Fl a +Shows the program profiling inlined in the assembly code only. +No C informations involving C sources are provided. +.It Fl h +Prints out informations about the usage of the tool. +.It Fl l Ar level +Changes the lower bound (expressed in percentage) for traced functions +that will be printed out in the report. +The default value is 0.5%. +.It Fl k Ar kerneldir +Set the pathname of the kernel directory to argument +.Ar kerneldir . +This directory specifies where +.Nm +should look for the kernel and its modules. +The default is +.Pa /boot/kernel . +.Sh LIMITATIONS +As long as +.Nm +relies on the +.Xr objdump 1 +utility to retrieve the C code, the program needs to be compiled with +debugging options. +Sometimes, in particular with heavy optimization levels, the +.Xr objdump 1 +utility embeds the code of inlining functions directly in the callers, +making an output difficult to read. +The x86 version reports the sampling from pmcstat collecting the following +instruction in regard of the interrupted one. +This means that the samples may be attributed to the line below the one +of interest. +.Sh SEE ALSO +.Xr objdump 1 , +.Xr pmcstat 8 +.Sh AUTHORS +.An Attilio Rao Aq attilio@FreeBSD.org Added: head/usr.sbin/pmcannotate/pmcannotate.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.sbin/pmcannotate/pmcannotate.c Wed Nov 26 21:44:57 2008 (r185347) @@ -0,0 +1,804 @@ +/*- + * Copyright (c) 2008 Nokia Corporation + * All rights reserved. + * + * This software was developed by Attilio Rao for the IPSO project under + * contract to Nokia Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/queue.h> + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <unistd.h> + +#define FNBUFF 161 +#define LNBUFF 161 + +#define TMPPATH "/tmp/pmcannotate.XXXXXX" + +#define FATAL(ptr, x ...) do { \ + fqueue_deleteall(); \ + general_deleteall(); \ + if ((ptr) != NULL) \ + perror(ptr); \ + fprintf(stderr, ##x); \ + remove(tbfl); \ + remove(tofl); \ + exit(EXIT_FAILURE); \ +} while (0) + +#define PERCSAMP(x) ((x) * 100 / totalsamples) + +struct entry { + TAILQ_ENTRY(entry) en_iter; + char *en_name; + uintptr_t en_pc; + uintptr_t en_ostart; + uintptr_t en_oend; + u_int en_nsamples; +}; + +struct aggent { + TAILQ_ENTRY(aggent) ag_fiter; + long ag_offset; + uintptr_t ag_ostart; + uintptr_t ag_oend; + char *ag_name; + u_int ag_nsamples; +}; + +static struct aggent *agg_create(const char *name, u_int nsamples, + uintptr_t start, uintptr_t end); +static void agg_destroy(struct aggent *agg) __unused; +static void asmparse(FILE *fp); +static int cparse(FILE *fp); +static void entry_acqref(struct entry *entry); +static struct entry *entry_create(const char *name, uintptr_t pc, + uintptr_t start, uintptr_t end); +static void entry_destroy(struct entry *entry) __unused; +static void fqueue_compact(float th); +static void fqueue_deleteall(void); +static struct aggent *fqueue_findent_by_name(const char *name); +static int fqueue_getall(const char *bin, char *temp, int asmf); +static int fqueue_insertent(struct entry *entry); +static int fqueue_insertgen(void); +static void general_deleteall(void); +static struct entry *general_findent(uintptr_t pc); +static void general_insertent(struct entry *entry); +static void general_printasm(FILE *fp, struct aggent *agg); +static int general_printc(FILE *fp, struct aggent *agg); +static int printblock(FILE *fp, struct aggent *agg); +static void usage(const char *progname) __dead2; + +static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst); +static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue); + +/* + * Use a float value in order to automatically promote operations + * to return a float value rather than use casts. + */ +static float totalsamples; + +/* + * Identifies a string cointaining objdump's assembly printout. + */ +static inline int +isasminline(const char *str) +{ + void *ptr; + int nbytes; + + if (isxdigit(str[1]) == 0) + return (0); + if (sscanf(str, " %p%n", &ptr, &nbytes) != 1) + return (0); + if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0) + return (0); + return (1); +} + +/* + * Identifies a string containing objdump's assembly printout + * for a new function. + */ +static inline int +newfunction(const char *str) +{ + char fname[FNBUFF]; + void *ptr; + int nbytes; + + if (isspace(str[0])) + return (0); + if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2) + return (0); + return (nbytes); +} + +/* + * Create a new first-level aggregation object for a specified + * function. + */ +static struct aggent * +agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end) +{ + struct aggent *agg; + + agg = calloc(1, sizeof(struct aggent)); + if (agg == NULL) + return (NULL); + agg->ag_name = strdup(name); + if (agg->ag_name == NULL) { + free(agg); + return (NULL); + } + agg->ag_nsamples = nsamples; + agg->ag_ostart = start; + agg->ag_oend = end; + return (agg); +} + +/* + * Destroy a first-level aggregation object for a specified + * function. + */ +static void +agg_destroy(struct aggent *agg) +{ + + free(agg->ag_name); + free(agg); +} + +/* + * Analyze the "objdump -d" output, locate functions and start + * printing out the assembly functions content. + * We do not use newfunction() because we actually need the + * function name in available form, but the heurstic used is + * the same. + */ +static void +asmparse(FILE *fp) +{ + char buffer[LNBUFF], fname[FNBUFF]; + struct aggent *agg; + void *ptr; + + while (fgets(buffer, LNBUFF, fp) != NULL) { + if (isspace(buffer[0])) + continue; + if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2) + continue; + agg = fqueue_findent_by_name(fname); + if (agg == NULL) + continue; + agg->ag_offset = ftell(fp); + } + + TAILQ_FOREACH(agg, &fqueue, ag_fiter) { + if (fseek(fp, agg->ag_offset, SEEK_SET) == -1) + return; + printf("Profile trace for function: %s() [%.2f%%]\n", + agg->ag_name, PERCSAMP(agg->ag_nsamples)); + general_printasm(fp, agg); + printf("\n"); + } +} + +/* + * Analyze the "objdump -S" output, locate functions and start + * printing out the C functions content. + * We do not use newfunction() because we actually need the + * function name in available form, but the heurstic used is + * the same. + * In order to maintain the printout sorted, on the first pass it + * simply stores the file offsets in order to fastly moved later + * (when the file is hot-cached also) when the real printout will + * happen. + */ +static int +cparse(FILE *fp) +{ + char buffer[LNBUFF], fname[FNBUFF]; + struct aggent *agg; + void *ptr; + + while (fgets(buffer, LNBUFF, fp) != NULL) { + if (isspace(buffer[0])) + continue; + if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2) + continue; + agg = fqueue_findent_by_name(fname); + if (agg == NULL) + continue; + agg->ag_offset = ftell(fp); + } + + TAILQ_FOREACH(agg, &fqueue, ag_fiter) { + if (fseek(fp, agg->ag_offset, SEEK_SET) == -1) + return (-1); + printf("Profile trace for function: %s() [%.2f%%]\n", + agg->ag_name, PERCSAMP(agg->ag_nsamples)); + if (general_printc(fp, agg) == -1) + return (-1); + printf("\n"); + } + return (0); +} + +/* + * Bump the number of samples for any raw entry. + */ +static void +entry_acqref(struct entry *entry) +{ + + entry->en_nsamples++; +} + +/* + * Create a new raw entry object for a specified function. + */ +static struct entry * +entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end) +{ + struct entry *obj; + + obj = calloc(1, sizeof(struct entry)); + if (obj == NULL) + return (NULL); + obj->en_name = strdup(name); + if (obj->en_name == NULL) { + free(obj); + return (NULL); + } + obj->en_pc = pc; + obj->en_ostart = start; + obj->en_oend = end; + obj->en_nsamples = 1; + return (obj); +} + +/* + * Destroy a raw entry object for a specified function. + */ +static void +entry_destroy(struct entry *entry) +{ + + free(entry->en_name); + free(entry); +} + +/* + * Specify a lower bound in percentage and drop from the + * first-level aggregation queue all the objects with a + * smaller impact. + */ +static void +fqueue_compact(float th) +{ + u_int thi; + struct aggent *agg, *tmpagg; + + if (totalsamples == 0) + return; + + /* Revert the percentage calculation. */ + thi = th * totalsamples / 100; + TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg) + if (agg->ag_nsamples < thi) + TAILQ_REMOVE(&fqueue, agg, ag_fiter); +} + +/* + * Flush the first-level aggregates queue. + */ +static void +fqueue_deleteall() +{ + struct aggent *agg; + + while (TAILQ_EMPTY(&fqueue) == 0) { + agg = TAILQ_FIRST(&fqueue); + TAILQ_REMOVE(&fqueue, agg, ag_fiter); + } +} + +/* + * Insert a raw entry into the aggregations queue. + * If the respective first-level aggregation object + * does not exist create it and maintain it sorted + * in respect of the number of samples. + */ +static int +fqueue_insertent(struct entry *entry) +{ + struct aggent *obj, *tmp; + int found; + + found = 0; + TAILQ_FOREACH(obj, &fqueue, ag_fiter) + if (!strcmp(obj->ag_name, entry->en_name)) { + found = 1; + obj->ag_nsamples += entry->en_nsamples; + break; + } + + /* + * If the firt-level aggregation object alredy exist, + * just aggregate the samples and, if needed, resort + * it. + */ + if (found) { + TAILQ_REMOVE(&fqueue, obj, ag_fiter); + found = 0; + TAILQ_FOREACH(tmp, &fqueue, ag_fiter) + if (obj->ag_nsamples > tmp->ag_nsamples) { + found = 1; + break; + } + if (found) + TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter); + else + TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter); + return (0); + } + + /* + * If the first-level aggregation object does not + * exist, create it and put in the sorted queue. + * If this is the first object, we need to set the + * head of the queue. + */ + obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart, + entry->en_oend); + if (obj == NULL) + return (-1); + if (TAILQ_EMPTY(&fqueue) != 0) { + TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter); + return (0); + } + TAILQ_FOREACH(tmp, &fqueue, ag_fiter) + if (obj->ag_nsamples > tmp->ag_nsamples) { + found = 1; + break; + } + if (found) + TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter); + else + TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter); + return (0); +} + +/* + * Lookup a first-level aggregation object by name. + */ +static struct aggent * +fqueue_findent_by_name(const char *name) +{ + struct aggent *obj; + + TAILQ_FOREACH(obj, &fqueue, ag_fiter) + if (!strcmp(obj->ag_name, name)) + return (obj); + return (NULL); +} + +/* + * Return the number of object in the first-level aggregations queue. + */ +static int +fqueue_getall(const char *bin, char *temp, int asmf) +{ + char tmpf[MAXPATHLEN * 2 + 50]; + struct aggent *agg; + uintptr_t start, end; + + if (mkstemp(temp) == -1) + return (-1); + TAILQ_FOREACH(agg, &fqueue, ag_fiter) { + bzero(tmpf, sizeof(tmpf)); + start = agg->ag_ostart; + end = agg->ag_oend; + + /* + * Fix-up the end address in order to show it in the objdump's + * trace. + */ + end++; + if (asmf) + snprintf(tmpf, sizeof(tmpf), + "objdump --start-address=%p " + "--stop-address=%p -d %s >> %s", (void *)start, + (void *)end, bin, temp); + else + snprintf(tmpf, sizeof(tmpf), + "objdump --start-address=%p " + "--stop-address=%p -S %s >> %s", (void *)start, + (void *)end, bin, temp); + if (system(tmpf) != 0) + return (-1); + } + return (0); +} + +/* + * Insert all the raw entries present in the general queue + * into the first-level aggregations queue. + */ +static int +fqueue_insertgen(void) +{ + struct entry *obj; + + TAILQ_FOREACH(obj, &mainlst, en_iter) + if (fqueue_insertent(obj) == -1) + return (-1); + return (0); +} + +/* + * Flush the raw entries general queue. + */ +static void +general_deleteall() +{ + struct entry *obj; + + while (TAILQ_EMPTY(&mainlst) == 0) { + obj = TAILQ_FIRST(&mainlst); + TAILQ_REMOVE(&mainlst, obj, en_iter); + } +} + +/* + * Lookup a raw entry by the PC. + */ +static struct entry * +general_findent(uintptr_t pc) +{ + struct entry *obj; + + TAILQ_FOREACH(obj, &mainlst, en_iter) + if (obj->en_pc == pc) + return (obj); + return (NULL); +} + +/* + * Insert a new raw entry in the general queue. + */ +static void +general_insertent(struct entry *entry) +{ + + TAILQ_INSERT_TAIL(&mainlst, entry, en_iter); +} + +/* + * Printout the body of an "objdump -d" assembly function. + * It does simply stops when a new function is encountered, + * bringing back the file position in order to not mess up + * subsequent analysis. + * C lines and others not recognized are simply skipped. + */ +static void +general_printasm(FILE *fp, struct aggent *agg) +{ + char buffer[LNBUFF]; + struct entry *obj; + int nbytes; + void *ptr; + + while (fgets(buffer, LNBUFF, fp) != NULL) { + if ((nbytes = newfunction(buffer)) != 0) { + fseek(fp, nbytes * -1, SEEK_CUR); + break; + } + if (!isasminline(buffer)) + continue; + if (sscanf(buffer, " %p:", &ptr) != 1) + continue; + obj = general_findent((uintptr_t)ptr); + if (obj == NULL) + printf("\t| %s", buffer); + else + printf("%.2f%%\t| %s", + (float)obj->en_nsamples * 100 / agg->ag_nsamples, + buffer); + } +} + +/* + * Printout the body of an "objdump -S" function. + * It does simply stops when a new function is encountered, + * bringing back the file position in order to not mess up + * subsequent analysis. + * It expect from the starting to the end to find, always, valid blocks + * (see below for an explanation of the "block" concept). + */ +static int +general_printc(FILE *fp, struct aggent *agg) +{ + char buffer[LNBUFF]; + + while (fgets(buffer, LNBUFF, fp) != NULL) { + fseek(fp, strlen(buffer) * -1, SEEK_CUR); + if (newfunction(buffer) != 0) + break; + if (printblock(fp, agg) == -1) + return (-1); + } + return (0); +} + +/* + * Printout a single block inside an "objdump -S" function. + * The block is composed of a first part in C and subsequent translation + * in assembly. + * This code also operates a second-level aggregation packing together + * samples relative to PCs into a (lower bottom) block with their + * C (higher half) counterpart. + */ +static int +printblock(FILE *fp, struct aggent *agg) +{ + char buffer[LNBUFF]; + long lstart; + struct entry *obj; + u_int tnsamples; + int done, nbytes, sentinel; + void *ptr; + + /* + * We expect the first thing of the block is C code, so simply give + * up if asm line is found. + */ + lstart = ftell(fp); + sentinel = 0; + for (;;) { + if (fgets(buffer, LNBUFF, fp) == NULL) + return (0); + if (isasminline(buffer) != 0) + break; + sentinel = 1; + nbytes = newfunction(buffer); + if (nbytes != 0) { + if (fseek(fp, nbytes * -1, SEEK_CUR) == -1) + return (-1); + return (0); + } + } + + /* + * If the sentinel is not set, it means it did not match any + * "high half" for this code so simply give up. + * Operates the second-level aggregation. + */ + tnsamples = 0; + do { + if (sentinel == 0) + return (-1); + if (sscanf(buffer, " %p:", &ptr) != 1) + return (-1); + obj = general_findent((uintptr_t)ptr); + if (obj != NULL) + tnsamples += obj->en_nsamples; + } while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0); + + /* Rewind to the start of the block in order to start the printout. */ + if (fseek(fp, lstart, SEEK_SET) == -1) + return (-1); + + /* Again the high half of the block rappresenting the C part. */ + done = 0; + while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) { + if (tnsamples == 0 || done != 0) + printf("\t| %s", buffer); + else { + done = 1; + printf("%.2f%%\t| %s", + (float)tnsamples * 100 / agg->ag_nsamples, buffer); + } + } + + /* + * Again the low half of the block rappresenting the asm + * translation part. + */ + for (;;) { + if (fgets(buffer, LNBUFF, fp) == NULL) + return (0); + if (isasminline(buffer) == 0) + break; + nbytes = newfunction(buffer); + if (nbytes != 0) { + if (fseek(fp, nbytes * -1, SEEK_CUR) == -1) + return (-1); + return (0); + } + } + if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1) + return (-1); + return (0); +} + +/* + * Helper printout functions. + */ +static void +usage(const char *progname) +{ + + fprintf(stderr, + "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n", + progname); + exit(EXIT_SUCCESS); +} + +int +main(int argc, char *argv[]) +{ + char buffer[LNBUFF], fname[FNBUFF], tbfl[] = TMPPATH, tofl[] = TMPPATH; + char tmpf[MAXPATHLEN * 2 + 50]; + float limit; + char *bin, *exec, *kfile, *ofile; + struct entry *obj; + FILE *gfp, *bfp; + void *ptr, *hstart, *hend; + uintptr_t tmppc, ostart, oend; + int cget, asmsrc; + + exec = argv[0]; + ofile = NULL; + bin = NULL; + kfile = NULL; + asmsrc = 0; + limit = 0.5; + while ((cget = getopt(argc, argv, "ahl:k:")) != -1) + switch(cget) { + case 'a': + asmsrc = 1; + break; + case 'k': + kfile = optarg; + break; + case 'l': + limit = (float)atof(optarg); + break; + case 'h': + case '?': + default: + usage(exec); + } + argc -= optind; + argv += optind; + if (argc != 2) + usage(exec); + ofile = argv[0]; + bin = argv[1]; + + if (access(bin, R_OK | F_OK) == -1) + FATAL(exec, "%s: Impossible to locate the binary file\n", + exec); + if (access(ofile, R_OK | F_OK) == -1) + FATAL(exec, "%s: Impossible to locate the pmcstat file\n", + exec); + if (kfile != NULL && access(kfile, R_OK | F_OK) == -1) + FATAL(exec, "%s: Impossible to locate the kernel file\n", + exec); + + bzero(tmpf, sizeof(tmpf)); + if (mkstemp(tofl) == -1) + FATAL(exec, "%s: Impossible to create the tmp file\n", + exec); + if (kfile != NULL) + snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s", + kfile, ofile, tofl); + else + snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile, + tofl); + if (system(tmpf) != 0) + FATAL(exec, "%s: Impossible to create the tmp file\n", + exec); + + gfp = fopen(tofl, "r"); + if (gfp == NULL) + FATAL(exec, "%s: Impossible to open the map file\n", + exec); + + /* + * Make the collection of raw entries from a pmcstat mapped file. + * The heuristic here wants strings in the form: + * "addr funcname startfaddr endfaddr". + */ + while (fgets(buffer, LNBUFF, gfp) != NULL) { + if (isspace(buffer[0])) + continue; + if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname, + &hstart, &hend) != 4) + FATAL(NULL, + "%s: Invalid scan of function in the map file\n", + exec); + ostart = (uintptr_t)hstart; + oend = (uintptr_t)hend; + tmppc = (uintptr_t)ptr; + totalsamples++; + obj = general_findent(tmppc); + if (obj != NULL) { + entry_acqref(obj); + continue; + } + obj = entry_create(fname, tmppc, ostart, oend); + if (obj == NULL) + FATAL(exec, + "%s: Impossible to create a new object\n", exec); + general_insertent(obj); + } + if (fclose(gfp) == EOF) + FATAL(exec, "%s: Impossible to close the filedesc\n", + exec); + if (remove(tofl) == -1) + FATAL(exec, "%s: Impossible to remove the tmpfile\n", + exec); + + /* + * Remove the loose end objects and feed the first-level aggregation + * queue. + */ + if (fqueue_insertgen() == -1) + FATAL(exec, "%s: Impossible to generate an analysis\n", + exec); + fqueue_compact(limit); + if (fqueue_getall(bin, tbfl, asmsrc) == -1) + FATAL(exec, "%s: Impossible to create the tmp file\n", + exec); + + bfp = fopen(tbfl, "r"); + if (bfp == NULL) + FATAL(exec, "%s: Impossible to open the binary file\n", + exec); + + if (asmsrc != 0) + asmparse(bfp); + else if (cparse(bfp) == -1) + FATAL(NULL, "%s: Invalid format for the C file\n", exec); + if (fclose(bfp) == EOF) + FATAL(exec, "%s: Impossible to close the filedesc\n", + exec); + if (remove(tbfl) == -1) + FATAL(exec, "%s: Impossible to remove the tmpfile\n", + exec); + return (0); +}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200811262144.mAQLiwLG067160>