Index: head/usr.sbin/Makefile =================================================================== --- head/usr.sbin/Makefile (revision 185346) +++ head/usr.sbin/Makefile (revision 185347) @@ -1,506 +1,508 @@ # From: @(#)Makefile 5.20 (Berkeley) 6/12/93 # $FreeBSD$ .include # XXX MISSING: mkproto SUBDIR= ${_ac} \ ${_accton} \ ${_acpi} \ adduser \ ${_amd} \ ${_ancontrol} \ ${_apm} \ ${_apmd} \ arp \ ${_asf} \ ${_atm} \ ${_audit} \ ${_auditd} \ ${_auditreduce} \ ${_authpf} \ ${_bluetooth} \ ${_boot0cfg} \ ${_boot98cfg} \ bootparamd \ ${_bsnmpd} \ ${_btxld} \ burncd \ cdcontrol \ chkgrp \ chown \ chroot \ ckdist \ clear_locks \ ${_config} \ ${_cpucontrol} \ crashinfo \ cron \ ${_crunch} \ ${_ctm} \ daemon \ dconschat \ devinfo \ digictl \ diskinfo \ ${_dnssec-keygen} \ ${_dnssec-signzone} \ dumpcis \ ${_editmap} \ ${_edquota} \ ${_eeprom} \ extattr \ extattrctl \ ${_faithd} \ ${_fdcontrol} \ ${_fdformat} \ ${_fdread} \ ${_fdwrite} \ fifolog \ ${_flowctl} \ ${_freebsd-update} \ ${_ftp-proxy} \ fwcontrol \ getfmac \ getpmac \ gstat \ ${_gssd} \ ifmcstat \ inetd \ iostat \ ${_ip6addrctl} \ ${_ipfwpcap} \ ${_IPXrouted} \ ${_jail} \ ${_jexec} \ ${_jls} \ ${_kbdcontrol} \ ${_kbdmap} \ ${_keyserv} \ ${_kgmon} \ ${_kgzip} \ kldxref \ lastlogin \ ${_lmcconfig} \ ${_lpr} \ ${_lptcontrol} \ ${_mailstats} \ mailwrapper \ ${_makemap} \ manctl \ memcontrol \ mergemaster \ mixer \ ${_mld6query} \ mlxcontrol \ mountd \ ${_mount_nwfs} \ mount_portalfs \ ${_mount_smbfs} \ ${_moused} \ ${_mptable} \ mtest \ mtree \ ${_named} \ ${_named-checkconf} \ ${_named-checkzone} \ ${_named.reload} \ ${_ndiscvt} \ ${_ndp} \ newsyslog \ nfsd \ ${_ngctl} \ ${_nghook} \ nologin \ ${_nscd} \ ${_ntp} \ ${_nvram} \ ${_ofwdump} \ pciconf \ periodic \ ${_pkg_install} \ + ${_pmcannotate} \ ${_pmccontrol} \ ${_pmcstat} \ ${_pnpinfo} \ ${_portsnap} \ powerd \ ${_ppp} \ ${_pppctl} \ ${_pppd} \ ${_pppstats} \ ${_praliases} \ ${_praudit} \ procctl \ pstat \ pw \ pwd_mkdb \ quot \ ${_quotaon} \ rarpd \ ${_raycontrol} \ ${_repquota} \ ${_rip6query} \ rmt \ ${_rndc} \ ${_rndc-confgen} \ ${_route6d} \ rpcbind \ rpc.lockd \ rpc.statd \ rpc.umntall \ ${_rpc.yppasswdd} \ ${_rpc.ypupdated} \ ${_rpc.ypxfrd} \ ${_rrenumd} \ ${_rtadvd} \ rtprio \ ${_rtsold} \ ${_rwhod} \ ${_sa} \ ${_sade} \ ${_sendmail} \ setfib \ setfmac \ setpmac \ ${_sicontrol} \ ${_sliplogin} \ ${_slstat} \ smbmsg \ snapinfo \ ${_spkrtest} \ spray \ ${_sysinstall} \ syslogd \ tcpdchk \ tcpdmatch \ tcpdrop \ tcpdump \ timed \ traceroute \ ${_traceroute6} \ trpt \ tzsetup \ ugidfw \ ${_usbdevs} \ ${_usbconfig} \ ${_vidcontrol} \ vipw \ watch \ watchdogd \ ${_wlandebug} \ ${_wlconfig} \ ${_wpa} \ ${_ypbind} \ ${_yp_mkdb} \ ${_yppoll} \ ${_yppush} \ ${_ypserv} \ ${_ypset} \ zic \ ${_zzz} # NB: keep these sorted by MK_* knobs .if ${MK_ACCT} != "no" _ac= ac _accton= accton _sa= sa .endif .if ${MK_AMD} != "no" _amd= amd .endif .if ${MK_AUDIT} != "no" _audit= audit _auditd= auditd _auditreduce= auditreduce _praudit= praudit .endif .if ${MK_AUTHPF} != "no" _authpf= authpf .endif .if ${MK_BIND_DNSSEC} != "no" && ${MK_OPENSSL} != "no" _dnssec-keygen= dnssec-keygen _dnssec-signzone= dnssec-signzone .endif .if ${MK_BIND_NAMED} != "no" _named= named _named-checkconf= named-checkconf _named-checkzone= named-checkzone _named.reload= named.reload _rndc= rndc _rndc-confgen= rndc-confgen .endif .if ${MK_BLUETOOTH} != "no" _bluetooth= bluetooth .endif .if ${MK_BSNMP} != "no" _bsnmpd= bsnmpd .endif .if ${MK_CTM} != "no" _ctm= ctm .endif .if ${MK_FLOPPY} != "no" _fdcontrol= fdcontrol _fdformat= fdformat _fdread= fdread _fdwrite= fdwrite .endif .if ${MK_FREEBSD_UPDATE} != "no" _freebsd-update= freebsd-update .endif .if ${MK_GSSAPI} != no _gssd= gssd .endif .if ${MK_INET6} != "no" _faithd= faithd _ip6addrctl= ip6addrctl _mld6query= mld6query _ndp= ndp _rip6query= rip6query _route6d= route6d _rrenumd= rrenumd _rtadvd= rtadvd _rtsold= rtsold _traceroute6= traceroute6 .endif .if ${MK_IPFW} != "no" _ipfwpcap= ipfwpcap .endif .if ${MK_IPX} != "no" _IPXrouted= IPXrouted .endif .if ${MK_JAIL} != "no" _jail= jail _jexec= jexec _jls= jls .endif # XXX MK_SYSCONS # XXX is moused w/ usb useful? .if ${MK_LEGACY_CONSOLE} != "no" _kbdcontrol= kbdcontrol _kbdmap= kbdmap _moused= moused _vidcontrol= vidcontrol .endif .if ${MK_LIBTHR} != "no" || ${MK_LIBPTHREAD} != "no" .if ${MK_PPP} != "no" _pppctl= pppctl .endif .if ${MK_NS_CACHING} != "no" _nscd= nscd .endif .endif .if ${MK_LPR} != "no" _lpr= lpr .endif .if ${MK_NETGRAPH} != "no" _flowctl= flowctl _lmcconfig= lmcconfig _ngctl= ngctl _nghook= nghook .endif .if ${MK_NIS} != "no" _rpc.yppasswdd= rpc.yppasswdd _rpc.ypupdated= rpc.ypupdated _rpc.ypxfrd= rpc.ypxfrd _ypbind= ypbind _yp_mkdb= yp_mkdb _yppoll= yppoll _yppush= yppush _ypserv= ypserv _ypset= ypset .endif .if ${MK_NTP} != "no" _ntp= ntp .endif .if ${MK_OPENSSL} != "no" _keyserv= keyserv .endif .if ${MK_PF} != "no" _ftp-proxy= ftp-proxy .endif .if ${MK_PKGTOOLS} != "no" _pkg_install= pkg_install .endif # XXX MK_TOOLCHAIN? .if ${MK_PMC} != "no" +_pmcannotate= pmcannotate _pmccontrol= pmccontrol _pmcstat= pmcstat .endif .if ${MK_PORTSNAP} != "no" _portsnap= portsnap .endif .if ${MK_PPP} != "no" _ppp= ppp #_pppctl handled below _pppd= pppd _pppstats= pppstats .endif .if ${MK_QUOTAS} != "no" _edquota= edquota _quotaon= quotaon _repquota= repquota .endif .if ${MK_RCMDS} != "no" _rwhod= rwhod .endif .if ${MK_SENDMAIL} != "no" _editmap= editmap _mailstats= mailstats _makemap= makemap _praliases= praliases _sendmail= sendmail .endif .if ${MK_SLIP} != "no" _sliplogin= sliplogin _slstat= slstat .endif .if ${MK_SYSINSTALL} != "no" .if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386" || \ ${MACHINE_ARCH} == "sparc64" _sade= sade .endif .if ${MACHINE_ARCH} != "arm" && ${MACHINE_ARCH} != "mips" _sysinstall= sysinstall .endif .endif .if ${MK_TOOLCHAIN} != "no" _config= config _crunch= crunch .endif .if ${MK_USB} != "no" _usbdevs= usbdevs _usbconfig= usbconfig .endif .if ${MK_WIRELESS} != "no" _ancontrol= ancontrol _raycontrol= raycontrol _wlandebug= wlandebug _wpa= wpa .endif .if ${MACHINE_ARCH} == "arm" _kgmon= kgmon .endif .if ${MACHINE_ARCH} == "i386" .if ${MK_APM} != "no" _apm= apm _apmd= apmd .endif _asf= asf .if ${MK_TOOLCHAIN} != "no" _btxld= btxld .endif _cpucontrol= cpucontrol _kgmon= kgmon _kgzip= kgzip _lptcontrol= lptcontrol .if ${MK_NCP} != "no" _mount_nwfs= mount_nwfs .endif _mount_smbfs= mount_smbfs _mptable= mptable .if ${MK_NDIS} != "no" _ndiscvt= ndiscvt .endif _pnpinfo= pnpinfo _sicontrol= sicontrol _spkrtest= spkrtest _zzz= zzz .if ${MACHINE} == "i386" .if ${MK_ACPI} != "no" _acpi= acpi .endif _boot0cfg= boot0cfg .if ${MK_WIRELESS} != "no" _wlconfig= wlconfig .endif .elif ${MACHINE} == "pc98" _boot98cfg= boot98cfg .endif .endif # kgzip: builds, but missing support files # mptable: broken (not 64 bit clean) # pnpinfo: crashes (not really useful anyway) .if ${MACHINE_ARCH} == "amd64" .if ${MK_ACPI} != "no" _acpi= acpi .endif _asf= asf _boot0cfg= boot0cfg .if ${MK_TOOLCHAIN} != "no" _btxld= btxld .endif _cpucontrol= cpucontrol _kgmon= kgmon _lptcontrol= lptcontrol .if ${MK_NCP} != "no" _mount_nwfs= mount_nwfs .endif _mount_smbfs= mount_smbfs _mptable= mptable .if ${MK_NDIS} != "no" _ndiscvt= ndiscvt .endif _sicontrol= sicontrol _spkrtest= spkrtest _zzz= zzz .endif .if ${MACHINE_ARCH} == "ia64" .if ${MK_ACPI} != "no" _acpi= acpi .endif _kgmon= kgmon _mount_smbfs= mount_smbfs _zzz= zzz .endif .if ${MACHINE_ARCH} == "powerpc" _mount_smbfs= mount_smbfs _nvram= nvram _ofwdump= ofwdump .endif .if ${MACHINE_ARCH} == "sparc64" _eeprom= eeprom _ofwdump= ofwdump .endif .include Index: head/usr.sbin/pmcannotate/Makefile =================================================================== --- head/usr.sbin/pmcannotate/Makefile (nonexistent) +++ head/usr.sbin/pmcannotate/Makefile (revision 185347) @@ -0,0 +1,12 @@ +# +# $FreeBSD$ +# + +PROG= pmcannotate +MAN= pmcannotate.8 + +WARNS?= 6 + +SRCS= pmcannotate.c + +.include Property changes on: head/usr.sbin/pmcannotate/Makefile ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/usr.sbin/pmcannotate/pmcannotate.8 =================================================================== --- head/usr.sbin/pmcannotate/pmcannotate.8 (nonexistent) +++ head/usr.sbin/pmcannotate/pmcannotate.8 (revision 185347) @@ -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 Property changes on: head/usr.sbin/pmcannotate/pmcannotate.8 ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/usr.sbin/pmcannotate/pmcannotate.c =================================================================== --- head/usr.sbin/pmcannotate/pmcannotate.c (nonexistent) +++ head/usr.sbin/pmcannotate/pmcannotate.c (revision 185347) @@ -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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include + +#include + +#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); +} Property changes on: head/usr.sbin/pmcannotate/pmcannotate.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property