aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/pmcannotate
diff options
context:
space:
mode:
authorAttilio Rao <attilio@FreeBSD.org>2008-11-26 21:44:57 +0000
committerAttilio Rao <attilio@FreeBSD.org>2008-11-26 21:44:57 +0000
commit1b4822a4130eb584a8399818082234af5131fb34 (patch)
treee4ad6f2a543e0d839e06cb771e99aa8996664a60 /usr.sbin/pmcannotate
parent86d6c2d4b99ea4c0ed1efe560dcd3b6ef558f90b (diff)
downloadsrc-1b4822a4130eb584a8399818082234af5131fb34.tar.gz
src-1b4822a4130eb584a8399818082234af5131fb34.zip
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
Notes
Notes: svn path=/head/; revision=185347
Diffstat (limited to 'usr.sbin/pmcannotate')
-rw-r--r--usr.sbin/pmcannotate/Makefile12
-rw-r--r--usr.sbin/pmcannotate/pmcannotate.8108
-rw-r--r--usr.sbin/pmcannotate/pmcannotate.c804
3 files changed, 924 insertions, 0 deletions
diff --git a/usr.sbin/pmcannotate/Makefile b/usr.sbin/pmcannotate/Makefile
new file mode 100644
index 000000000000..e087bb3c28e7
--- /dev/null
+++ b/usr.sbin/pmcannotate/Makefile
@@ -0,0 +1,12 @@
+#
+# $FreeBSD$
+#
+
+PROG= pmcannotate
+MAN= pmcannotate.8
+
+WARNS?= 6
+
+SRCS= pmcannotate.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/pmcannotate/pmcannotate.8 b/usr.sbin/pmcannotate/pmcannotate.8
new file mode 100644
index 000000000000..38a413ac5f92
--- /dev/null
+++ b/usr.sbin/pmcannotate/pmcannotate.8
@@ -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
diff --git a/usr.sbin/pmcannotate/pmcannotate.c b/usr.sbin/pmcannotate/pmcannotate.c
new file mode 100644
index 000000000000..1a5dec2b6d13
--- /dev/null
+++ b/usr.sbin/pmcannotate/pmcannotate.c
@@ -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);
+}