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
This commit is contained in:
parent
86d6c2d4b9
commit
1b4822a413
@ -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
|
||||
|
12
usr.sbin/pmcannotate/Makefile
Normal file
12
usr.sbin/pmcannotate/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# $FreeBSD$
|
||||
#
|
||||
|
||||
PROG= pmcannotate
|
||||
MAN= pmcannotate.8
|
||||
|
||||
WARNS?= 6
|
||||
|
||||
SRCS= pmcannotate.c
|
||||
|
||||
.include <bsd.prog.mk>
|
108
usr.sbin/pmcannotate/pmcannotate.8
Normal file
108
usr.sbin/pmcannotate/pmcannotate.8
Normal file
@ -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
|
804
usr.sbin/pmcannotate/pmcannotate.c
Normal file
804
usr.sbin/pmcannotate/pmcannotate.c
Normal file
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user