svn commit: r185347 - in head/usr.sbin: . pmcannotate

Attilio Rao attilio at FreeBSD.org
Wed Nov 26 13:44:58 PST 2008


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 at 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);
+}


More information about the svn-src-all mailing list