- Reorganize code in 'plugin' to share log processing.
- Kcachegrind (calltree) support with assembly/source code mapping and call count estimator (-F). - Top mode for calltree and callgraph plugin (-T). MFC after: 1 month
This commit is contained in:
parent
af002ab819
commit
0b86b1bb01
@ -6,8 +6,9 @@ PROG= pmcstat
|
||||
MAN= pmcstat.8
|
||||
|
||||
DPADD= ${LIBELF} ${LIBKVM} ${LIBPMC} ${LIBM}
|
||||
LDADD= -lelf -lkvm -lpmc -lm
|
||||
LDADD= -lelf -lkvm -lpmc -lm -lncurses
|
||||
|
||||
SRCS= pmcstat.c pmcstat.h pmcstat_log.c
|
||||
SRCS= pmcstat.c pmcstat.h pmcstat_log.c \
|
||||
pmcpl_callgraph.c pmcpl_gprof.c pmcpl_annotate.c pmcpl_calltree.c
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
|
111
usr.sbin/pmcstat/pmcpl_annotate.c
Normal file
111
usr.sbin/pmcstat/pmcpl_annotate.c
Normal file
@ -0,0 +1,111 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Transform a hwpmc(4) log into human readable form, and into
|
||||
* gprof(1) compatible profiles.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/gmon.h>
|
||||
#include <sys/imgact_aout.h>
|
||||
#include <sys/imgact_elf.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/pmc.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gelf.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <pmc.h>
|
||||
#include <pmclog.h>
|
||||
#include <sysexits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pmcstat.h"
|
||||
#include "pmcstat_log.h"
|
||||
#include "pmcpl_annotate.h"
|
||||
|
||||
/*
|
||||
* Record a callchain.
|
||||
*/
|
||||
|
||||
void
|
||||
pmcpl_annotate_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
|
||||
{
|
||||
struct pmcstat_pcmap *map;
|
||||
struct pmcstat_symbol *sym;
|
||||
uintfptr_t newpc;
|
||||
struct pmcstat_image *image;
|
||||
|
||||
(void) pmcr; (void) nsamples; (void) usermode; (void) cpu;
|
||||
|
||||
map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]);
|
||||
if (map == NULL) {
|
||||
/* Unknown offset. */
|
||||
pmcstat_stats.ps_samples_unknown_offset++;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc);
|
||||
|
||||
image = map->ppm_image;
|
||||
newpc = cc[0] - (map->ppm_lowpc +
|
||||
(image->pi_vaddr - image->pi_start));
|
||||
sym = pmcstat_symbol_search(image, newpc);
|
||||
if (sym == NULL)
|
||||
return;
|
||||
|
||||
fprintf(args.pa_graphfile, "%p %s 0x%jx 0x%jx\n",
|
||||
(void *)cc[0],
|
||||
pmcstat_string_unintern(sym->ps_name),
|
||||
(uintmax_t)(sym->ps_start +
|
||||
image->pi_vaddr), (uintmax_t)(sym->ps_end +
|
||||
image->pi_vaddr));
|
||||
}
|
41
usr.sbin/pmcstat/pmcpl_annotate.h
Normal file
41
usr.sbin/pmcstat/pmcpl_annotate.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_PL_ANNOTATE_H_
|
||||
#define _PMCSTAT_PL_ANNOTATE_H_
|
||||
|
||||
/* Function prototypes */
|
||||
void pmcpl_annotate_process(
|
||||
struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
|
||||
|
||||
#endif /* _PMCSTAT_PL_ANNOTATE_H_ */
|
682
usr.sbin/pmcstat/pmcpl_callgraph.c
Normal file
682
usr.sbin/pmcstat/pmcpl_callgraph.c
Normal file
@ -0,0 +1,682 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Transform a hwpmc(4) log into human readable form, and into
|
||||
* gprof(1) compatible profiles.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/gmon.h>
|
||||
#include <sys/imgact_aout.h>
|
||||
#include <sys/imgact_elf.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/pmc.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <curses.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gelf.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <pmc.h>
|
||||
#include <pmclog.h>
|
||||
#include <sysexits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pmcstat.h"
|
||||
#include "pmcstat_log.h"
|
||||
#include "pmcstat_top.h"
|
||||
#include "pmcpl_callgraph.h"
|
||||
|
||||
/* Get the sample value in percent related to nsamples. */
|
||||
#define PMCPL_CG_COUNTP(a) \
|
||||
((a)->pcg_count * 100.0 / nsamples)
|
||||
|
||||
/*
|
||||
* The toplevel CG nodes (i.e., with rank == 0) are placed in a hash table.
|
||||
*/
|
||||
|
||||
struct pmcstat_cgnode_hash_list pmcstat_cgnode_hash[PMCSTAT_NHASH];
|
||||
int pmcstat_cgnode_hash_count;
|
||||
|
||||
static pmcstat_interned_string pmcstat_previous_filename_printed;
|
||||
|
||||
static struct pmcstat_cgnode *
|
||||
pmcstat_cgnode_allocate(struct pmcstat_image *image, uintfptr_t pc)
|
||||
{
|
||||
struct pmcstat_cgnode *cg;
|
||||
|
||||
if ((cg = malloc(sizeof(*cg))) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot allocate callgraph node");
|
||||
|
||||
cg->pcg_image = image;
|
||||
cg->pcg_func = pc;
|
||||
|
||||
cg->pcg_count = 0;
|
||||
cg->pcg_nchildren = 0;
|
||||
LIST_INIT(&cg->pcg_children);
|
||||
|
||||
return (cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a node and its children.
|
||||
*/
|
||||
static void
|
||||
pmcstat_cgnode_free(struct pmcstat_cgnode *cg)
|
||||
{
|
||||
struct pmcstat_cgnode *cgc, *cgtmp;
|
||||
|
||||
LIST_FOREACH_SAFE(cgc, &cg->pcg_children, pcg_sibling, cgtmp)
|
||||
pmcstat_cgnode_free(cgc);
|
||||
free(cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look for a callgraph node associated with pmc `pmcid' in the global
|
||||
* hash table that corresponds to the given `pc' value in the process
|
||||
* `pp'.
|
||||
*/
|
||||
static struct pmcstat_cgnode *
|
||||
pmcstat_cgnode_hash_lookup_pc(struct pmcstat_process *pp, pmc_id_t pmcid,
|
||||
uintfptr_t pc, int usermode)
|
||||
{
|
||||
struct pmcstat_pcmap *ppm;
|
||||
struct pmcstat_symbol *sym;
|
||||
struct pmcstat_image *image;
|
||||
struct pmcstat_cgnode *cg;
|
||||
struct pmcstat_cgnode_hash *h;
|
||||
uintfptr_t loadaddress;
|
||||
unsigned int i, hash;
|
||||
|
||||
ppm = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, pc);
|
||||
if (ppm == NULL)
|
||||
return (NULL);
|
||||
|
||||
image = ppm->ppm_image;
|
||||
|
||||
loadaddress = ppm->ppm_lowpc + image->pi_vaddr - image->pi_start;
|
||||
pc -= loadaddress; /* Convert to an offset in the image. */
|
||||
|
||||
/*
|
||||
* Try determine the function at this offset. If we can't
|
||||
* find a function round leave the `pc' value alone.
|
||||
*/
|
||||
if ((sym = pmcstat_symbol_search(image, pc)) != NULL)
|
||||
pc = sym->ps_start;
|
||||
|
||||
for (hash = i = 0; i < sizeof(uintfptr_t); i++)
|
||||
hash += (pc >> i) & 0xFF;
|
||||
|
||||
hash &= PMCSTAT_HASH_MASK;
|
||||
|
||||
cg = NULL;
|
||||
LIST_FOREACH(h, &pmcstat_cgnode_hash[hash], pch_next)
|
||||
{
|
||||
if (h->pch_pmcid != pmcid)
|
||||
continue;
|
||||
|
||||
cg = h->pch_cgnode;
|
||||
|
||||
assert(cg != NULL);
|
||||
|
||||
if (cg->pcg_image == image && cg->pcg_func == pc)
|
||||
return (cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* We haven't seen this (pmcid, pc) tuple yet, so allocate a
|
||||
* new callgraph node and a new hash table entry for it.
|
||||
*/
|
||||
cg = pmcstat_cgnode_allocate(image, pc);
|
||||
if ((h = malloc(sizeof(*h))) == NULL)
|
||||
err(EX_OSERR, "ERROR: Could not allocate callgraph node");
|
||||
|
||||
h->pch_pmcid = pmcid;
|
||||
h->pch_cgnode = cg;
|
||||
LIST_INSERT_HEAD(&pmcstat_cgnode_hash[hash], h, pch_next);
|
||||
|
||||
pmcstat_cgnode_hash_count++;
|
||||
|
||||
return (cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare two callgraph nodes for sorting.
|
||||
*/
|
||||
static int
|
||||
pmcstat_cgnode_compare(const void *a, const void *b)
|
||||
{
|
||||
const struct pmcstat_cgnode *const *pcg1, *const *pcg2, *cg1, *cg2;
|
||||
|
||||
pcg1 = (const struct pmcstat_cgnode *const *) a;
|
||||
cg1 = *pcg1;
|
||||
pcg2 = (const struct pmcstat_cgnode *const *) b;
|
||||
cg2 = *pcg2;
|
||||
|
||||
/* Sort in reverse order */
|
||||
if (cg1->pcg_count < cg2->pcg_count)
|
||||
return (1);
|
||||
if (cg1->pcg_count > cg2->pcg_count)
|
||||
return (-1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find (allocating if a needed) a callgraph node in the given
|
||||
* parent with the same (image, pcoffset) pair.
|
||||
*/
|
||||
|
||||
static struct pmcstat_cgnode *
|
||||
pmcstat_cgnode_find(struct pmcstat_cgnode *parent, struct pmcstat_image *image,
|
||||
uintfptr_t pcoffset)
|
||||
{
|
||||
struct pmcstat_cgnode *child;
|
||||
|
||||
LIST_FOREACH(child, &parent->pcg_children, pcg_sibling) {
|
||||
if (child->pcg_image == image &&
|
||||
child->pcg_func == pcoffset)
|
||||
return (child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new structure.
|
||||
*/
|
||||
|
||||
child = pmcstat_cgnode_allocate(image, pcoffset);
|
||||
|
||||
/*
|
||||
* Link it into the parent.
|
||||
*/
|
||||
LIST_INSERT_HEAD(&parent->pcg_children, child, pcg_sibling);
|
||||
parent->pcg_nchildren++;
|
||||
|
||||
return (child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print one callgraph node. The output format is:
|
||||
*
|
||||
* indentation %(parent's samples) #nsamples function@object
|
||||
*/
|
||||
static void
|
||||
pmcstat_cgnode_print(struct pmcstat_cgnode *cg, int depth, uint32_t total)
|
||||
{
|
||||
uint32_t n;
|
||||
const char *space;
|
||||
struct pmcstat_symbol *sym;
|
||||
struct pmcstat_cgnode **sortbuffer, **cgn, *pcg;
|
||||
|
||||
space = " ";
|
||||
|
||||
if (depth > 0)
|
||||
(void) fprintf(args.pa_graphfile, "%*s", depth, space);
|
||||
|
||||
if (cg->pcg_count == total)
|
||||
(void) fprintf(args.pa_graphfile, "100.0%% ");
|
||||
else
|
||||
(void) fprintf(args.pa_graphfile, "%05.2f%% ",
|
||||
100.0 * cg->pcg_count / total);
|
||||
|
||||
n = fprintf(args.pa_graphfile, " [%u] ", cg->pcg_count);
|
||||
|
||||
/* #samples is a 12 character wide field. */
|
||||
if (n < 12)
|
||||
(void) fprintf(args.pa_graphfile, "%*s", 12 - n, space);
|
||||
|
||||
if (depth > 0)
|
||||
(void) fprintf(args.pa_graphfile, "%*s", depth, space);
|
||||
|
||||
sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func);
|
||||
if (sym)
|
||||
(void) fprintf(args.pa_graphfile, "%s",
|
||||
pmcstat_string_unintern(sym->ps_name));
|
||||
else
|
||||
(void) fprintf(args.pa_graphfile, "%p",
|
||||
(void *) (cg->pcg_image->pi_vaddr + cg->pcg_func));
|
||||
|
||||
if (pmcstat_previous_filename_printed !=
|
||||
cg->pcg_image->pi_fullpath) {
|
||||
pmcstat_previous_filename_printed = cg->pcg_image->pi_fullpath;
|
||||
(void) fprintf(args.pa_graphfile, " @ %s\n",
|
||||
pmcstat_string_unintern(
|
||||
pmcstat_previous_filename_printed));
|
||||
} else
|
||||
(void) fprintf(args.pa_graphfile, "\n");
|
||||
|
||||
if (cg->pcg_nchildren == 0)
|
||||
return;
|
||||
|
||||
if ((sortbuffer = (struct pmcstat_cgnode **)
|
||||
malloc(sizeof(struct pmcstat_cgnode *) *
|
||||
cg->pcg_nchildren)) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot print callgraph");
|
||||
cgn = sortbuffer;
|
||||
|
||||
LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling)
|
||||
*cgn++ = pcg;
|
||||
|
||||
assert(cgn - sortbuffer == (int) cg->pcg_nchildren);
|
||||
|
||||
qsort(sortbuffer, cg->pcg_nchildren, sizeof(struct pmcstat_cgnode *),
|
||||
pmcstat_cgnode_compare);
|
||||
|
||||
for (cgn = sortbuffer, n = 0; n < cg->pcg_nchildren; n++, cgn++)
|
||||
pmcstat_cgnode_print(*cgn, depth+1, cg->pcg_count);
|
||||
|
||||
free(sortbuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a callchain.
|
||||
*/
|
||||
|
||||
void
|
||||
pmcpl_cg_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
|
||||
{
|
||||
uintfptr_t pc, loadaddress;
|
||||
uint32_t n;
|
||||
struct pmcstat_image *image;
|
||||
struct pmcstat_pcmap *ppm;
|
||||
struct pmcstat_symbol *sym;
|
||||
struct pmcstat_cgnode *parent, *child;
|
||||
struct pmcstat_process *km;
|
||||
pmc_id_t pmcid;
|
||||
|
||||
(void) cpu;
|
||||
|
||||
/*
|
||||
* Find the callgraph node recorded in the global hash table
|
||||
* for this (pmcid, pc).
|
||||
*/
|
||||
|
||||
pc = cc[0];
|
||||
pmcid = pmcr->pr_pmcid;
|
||||
parent = pmcstat_cgnode_hash_lookup_pc(pp, pmcid, pc, usermode);
|
||||
if (parent == NULL) {
|
||||
pmcstat_stats.ps_callchain_dubious_frames++;
|
||||
return;
|
||||
}
|
||||
|
||||
parent->pcg_count++;
|
||||
|
||||
/*
|
||||
* For each return address in the call chain record, subject
|
||||
* to the maximum depth desired.
|
||||
* - Find the image associated with the sample. Stop if there
|
||||
* there is no valid image at that address.
|
||||
* - Find the function that overlaps the return address.
|
||||
* - If found: use the start address of the function.
|
||||
* If not found (say an object's symbol table is not present or
|
||||
* is incomplete), round down to th gprof bucket granularity.
|
||||
* - Convert return virtual address to an offset in the image.
|
||||
* - Look for a child with the same {offset,image} tuple,
|
||||
* inserting one if needed.
|
||||
* - Increment the count of occurrences of the child.
|
||||
*/
|
||||
km = pmcstat_kernproc;
|
||||
|
||||
for (n = 1; n < (uint32_t) args.pa_graphdepth && n < nsamples; n++,
|
||||
parent = child) {
|
||||
pc = cc[n];
|
||||
|
||||
ppm = pmcstat_process_find_map(usermode ? pp : km, pc);
|
||||
if (ppm == NULL) {
|
||||
/* Detect full frame capture (kernel + user). */
|
||||
if (!usermode) {
|
||||
ppm = pmcstat_process_find_map(pp, pc);
|
||||
if (ppm != NULL)
|
||||
km = pp;
|
||||
}
|
||||
}
|
||||
if (ppm == NULL)
|
||||
return;
|
||||
|
||||
image = ppm->ppm_image;
|
||||
loadaddress = ppm->ppm_lowpc + image->pi_vaddr -
|
||||
image->pi_start;
|
||||
pc -= loadaddress;
|
||||
|
||||
if ((sym = pmcstat_symbol_search(image, pc)) != NULL)
|
||||
pc = sym->ps_start;
|
||||
|
||||
child = pmcstat_cgnode_find(parent, image, pc);
|
||||
child->pcg_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Printing a callgraph for a PMC.
|
||||
*/
|
||||
static void
|
||||
pmcstat_callgraph_print_for_pmcid(struct pmcstat_pmcrecord *pmcr)
|
||||
{
|
||||
int n, nentries;
|
||||
uint32_t nsamples;
|
||||
pmc_id_t pmcid;
|
||||
struct pmcstat_cgnode **sortbuffer, **cgn;
|
||||
struct pmcstat_cgnode_hash *pch;
|
||||
|
||||
/*
|
||||
* We pull out all callgraph nodes in the top-level hash table
|
||||
* with a matching PMC id. We then sort these based on the
|
||||
* frequency of occurrence. Each callgraph node is then
|
||||
* printed.
|
||||
*/
|
||||
|
||||
nsamples = 0;
|
||||
pmcid = pmcr->pr_pmcid;
|
||||
if ((sortbuffer = (struct pmcstat_cgnode **)
|
||||
malloc(sizeof(struct pmcstat_cgnode *) *
|
||||
pmcstat_cgnode_hash_count)) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot sort callgraph");
|
||||
cgn = sortbuffer;
|
||||
|
||||
for (n = 0; n < PMCSTAT_NHASH; n++)
|
||||
LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
|
||||
if (pch->pch_pmcid == pmcid) {
|
||||
nsamples += pch->pch_cgnode->pcg_count;
|
||||
*cgn++ = pch->pch_cgnode;
|
||||
}
|
||||
|
||||
nentries = cgn - sortbuffer;
|
||||
assert(nentries <= pmcstat_cgnode_hash_count);
|
||||
|
||||
if (nentries == 0) {
|
||||
free(sortbuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *),
|
||||
pmcstat_cgnode_compare);
|
||||
|
||||
(void) fprintf(args.pa_graphfile,
|
||||
"@ %s [%u samples]\n\n",
|
||||
pmcstat_string_unintern(pmcr->pr_pmcname),
|
||||
nsamples);
|
||||
|
||||
for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) {
|
||||
pmcstat_previous_filename_printed = NULL;
|
||||
pmcstat_cgnode_print(*cgn, 0, nsamples);
|
||||
(void) fprintf(args.pa_graphfile, "\n");
|
||||
}
|
||||
|
||||
free(sortbuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print out callgraphs.
|
||||
*/
|
||||
|
||||
static void
|
||||
pmcstat_callgraph_print(void)
|
||||
{
|
||||
struct pmcstat_pmcrecord *pmcr;
|
||||
|
||||
LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next)
|
||||
pmcstat_callgraph_print_for_pmcid(pmcr);
|
||||
}
|
||||
|
||||
static void
|
||||
pmcstat_cgnode_topprint(struct pmcstat_cgnode *cg,
|
||||
int depth, uint32_t nsamples)
|
||||
{
|
||||
int v_attrs, vs_len, ns_len, width, len, n, nchildren;
|
||||
float v;
|
||||
char ns[30], vs[10];
|
||||
struct pmcstat_symbol *sym;
|
||||
struct pmcstat_cgnode **sortbuffer, **cgn, *pcg;
|
||||
|
||||
(void) depth;
|
||||
|
||||
/* Format value. */
|
||||
v = PMCPL_CG_COUNTP(cg);
|
||||
snprintf(vs, sizeof(vs), "%.1f", v);
|
||||
v_attrs = PMCSTAT_ATTRPERCENT(v);
|
||||
|
||||
/* Format name. */
|
||||
sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func);
|
||||
if (sym != NULL) {
|
||||
snprintf(ns, sizeof(ns), "%s",
|
||||
pmcstat_string_unintern(sym->ps_name));
|
||||
} else
|
||||
snprintf(ns, sizeof(ns), "%p",
|
||||
(void *)cg->pcg_func);
|
||||
|
||||
PMCSTAT_ATTRON(v_attrs);
|
||||
PMCSTAT_PRINTW("%5.5s", vs);
|
||||
PMCSTAT_ATTROFF(v_attrs);
|
||||
PMCSTAT_PRINTW(" %-10.10s %-20.20s",
|
||||
pmcstat_string_unintern(cg->pcg_image->pi_name),
|
||||
ns);
|
||||
|
||||
nchildren = cg->pcg_nchildren;
|
||||
if (nchildren == 0) {
|
||||
PMCSTAT_PRINTW("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
width = pmcstat_displaywidth - 40;
|
||||
|
||||
if ((sortbuffer = (struct pmcstat_cgnode **)
|
||||
malloc(sizeof(struct pmcstat_cgnode *) *
|
||||
nchildren)) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot print callgraph");
|
||||
cgn = sortbuffer;
|
||||
|
||||
LIST_FOREACH(pcg, &cg->pcg_children, pcg_sibling)
|
||||
*cgn++ = pcg;
|
||||
|
||||
assert(cgn - sortbuffer == (int)nchildren);
|
||||
|
||||
qsort(sortbuffer, nchildren, sizeof(struct pmcstat_cgnode *),
|
||||
pmcstat_cgnode_compare);
|
||||
|
||||
/* Count how many callers. */
|
||||
for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) {
|
||||
pcg = *cgn;
|
||||
|
||||
v = PMCPL_CG_COUNTP(pcg);
|
||||
if (v < pmcstat_threshold)
|
||||
break;
|
||||
}
|
||||
nchildren = n;
|
||||
|
||||
for (cgn = sortbuffer, n = 0; n < nchildren; n++, cgn++) {
|
||||
pcg = *cgn;
|
||||
|
||||
/* Format value. */
|
||||
if (nchildren > 1) {
|
||||
v = PMCPL_CG_COUNTP(pcg);
|
||||
vs_len = snprintf(vs, sizeof(vs), ":%.1f", v);
|
||||
v_attrs = PMCSTAT_ATTRPERCENT(v);
|
||||
} else
|
||||
vs_len = 0;
|
||||
|
||||
/* Format name. */
|
||||
sym = pmcstat_symbol_search(pcg->pcg_image, pcg->pcg_func);
|
||||
if (sym != NULL) {
|
||||
ns_len = snprintf(ns, sizeof(ns), "%s",
|
||||
pmcstat_string_unintern(sym->ps_name));
|
||||
} else
|
||||
ns_len = snprintf(ns, sizeof(ns), "%p",
|
||||
(void *)pcg->pcg_func);
|
||||
|
||||
len = ns_len + vs_len + 1;
|
||||
if (width - len < 0) {
|
||||
PMCSTAT_PRINTW("...");
|
||||
break;
|
||||
}
|
||||
width -= len;
|
||||
|
||||
PMCSTAT_PRINTW(" %s", ns);
|
||||
if (nchildren > 1) {
|
||||
PMCSTAT_ATTRON(v_attrs);
|
||||
PMCSTAT_PRINTW("%s", vs);
|
||||
PMCSTAT_ATTROFF(v_attrs);
|
||||
}
|
||||
}
|
||||
PMCSTAT_PRINTW("\n");
|
||||
free(sortbuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Top mode display.
|
||||
*/
|
||||
|
||||
void
|
||||
pmcpl_cg_topdisplay(void)
|
||||
{
|
||||
int n, nentries;
|
||||
uint32_t nsamples;
|
||||
struct pmcstat_cgnode **sortbuffer, **cgn;
|
||||
struct pmcstat_cgnode_hash *pch;
|
||||
struct pmcstat_pmcrecord *pmcr;
|
||||
|
||||
pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter);
|
||||
|
||||
/*
|
||||
* We pull out all callgraph nodes in the top-level hash table
|
||||
* with a matching PMC index. We then sort these based on the
|
||||
* frequency of occurrence. Each callgraph node is then
|
||||
* printed.
|
||||
*/
|
||||
|
||||
nsamples = 0;
|
||||
|
||||
if ((sortbuffer = (struct pmcstat_cgnode **)
|
||||
malloc(sizeof(struct pmcstat_cgnode *) *
|
||||
pmcstat_cgnode_hash_count)) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot sort callgraph");
|
||||
cgn = sortbuffer;
|
||||
|
||||
for (n = 0; n < PMCSTAT_NHASH; n++)
|
||||
LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
|
||||
if (pmcr == NULL || pch->pch_pmcid == pmcr->pr_pmcid) {
|
||||
nsamples += pch->pch_cgnode->pcg_count;
|
||||
*cgn++ = pch->pch_cgnode;
|
||||
}
|
||||
|
||||
nentries = cgn - sortbuffer;
|
||||
assert(nentries <= pmcstat_cgnode_hash_count);
|
||||
|
||||
if (nentries == 0) {
|
||||
free(sortbuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
qsort(sortbuffer, nentries, sizeof(struct pmcstat_cgnode *),
|
||||
pmcstat_cgnode_compare);
|
||||
|
||||
PMCSTAT_PRINTW("%5.5s %-10.10s %-20.20s %s\n",
|
||||
"%SAMP", "IMAGE", "FUNCTION", "CALLERS");
|
||||
|
||||
nentries = min(pmcstat_displayheight - 2, nentries);
|
||||
|
||||
for (cgn = sortbuffer, n = 0; n < nentries; n++, cgn++) {
|
||||
if (PMCPL_CG_COUNTP(*cgn) < pmcstat_threshold)
|
||||
break;
|
||||
pmcstat_cgnode_topprint(*cgn, 0, nsamples);
|
||||
}
|
||||
|
||||
free(sortbuffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle top mode keypress.
|
||||
*/
|
||||
|
||||
int
|
||||
pmcpl_cg_topkeypress(int c, WINDOW *w)
|
||||
{
|
||||
|
||||
(void) c; (void) w;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
pmcpl_cg_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
pmcstat_cgnode_hash_count = 0;
|
||||
pmcstat_previous_filename_printed = NULL;
|
||||
|
||||
for (i = 0; i < PMCSTAT_NHASH; i++) {
|
||||
LIST_INIT(&pmcstat_cgnode_hash[i]);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
pmcpl_cg_shutdown(FILE *mf)
|
||||
{
|
||||
int i;
|
||||
struct pmcstat_cgnode_hash *pch, *pchtmp;
|
||||
|
||||
(void) mf;
|
||||
|
||||
if (args.pa_flags & FLAG_DO_CALLGRAPHS)
|
||||
pmcstat_callgraph_print();
|
||||
|
||||
/*
|
||||
* Free memory.
|
||||
*/
|
||||
for (i = 0; i < PMCSTAT_NHASH; i++) {
|
||||
LIST_FOREACH_SAFE(pch, &pmcstat_cgnode_hash[i], pch_next,
|
||||
pchtmp) {
|
||||
pmcstat_cgnode_free(pch->pch_cgnode);
|
||||
LIST_REMOVE(pch, pch_next);
|
||||
free(pch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
usr.sbin/pmcstat/pmcpl_callgraph.h
Normal file
67
usr.sbin/pmcstat/pmcpl_callgraph.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_PL_CALLGRAPH_H_
|
||||
#define _PMCSTAT_PL_CALLGRAPH_H_
|
||||
|
||||
/*
|
||||
* Each call graph node is tracked by a pmcstat_cgnode struct.
|
||||
*/
|
||||
|
||||
struct pmcstat_cgnode {
|
||||
struct pmcstat_image *pcg_image;
|
||||
uintfptr_t pcg_func;
|
||||
uint32_t pcg_count;
|
||||
uint32_t pcg_nchildren;
|
||||
LIST_ENTRY(pmcstat_cgnode) pcg_sibling;
|
||||
LIST_HEAD(,pmcstat_cgnode) pcg_children;
|
||||
};
|
||||
|
||||
struct pmcstat_cgnode_hash {
|
||||
struct pmcstat_cgnode *pch_cgnode;
|
||||
pmc_id_t pch_pmcid;
|
||||
LIST_ENTRY(pmcstat_cgnode_hash) pch_next;
|
||||
};
|
||||
extern LIST_HEAD(pmcstat_cgnode_hash_list, pmcstat_cgnode_hash) pmcstat_cgnode_hash[PMCSTAT_NHASH];
|
||||
extern int pmcstat_cgnode_hash_count;
|
||||
|
||||
/* Function prototypes */
|
||||
int pmcpl_cg_init(void);
|
||||
void pmcpl_cg_shutdown(FILE *mf);
|
||||
void pmcpl_cg_process(
|
||||
struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
|
||||
int pmcpl_cg_topkeypress(int c, WINDOW *w);
|
||||
void pmcpl_cg_topdisplay(void);
|
||||
void pmcpl_cg_configure(char *opt);
|
||||
|
||||
#endif /* _PMCSTAT_PL_CALLGRAPH_H_ */
|
1000
usr.sbin/pmcstat/pmcpl_calltree.c
Normal file
1000
usr.sbin/pmcstat/pmcpl_calltree.c
Normal file
File diff suppressed because it is too large
Load Diff
42
usr.sbin/pmcstat/pmcpl_calltree.h
Normal file
42
usr.sbin/pmcstat/pmcpl_calltree.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*-
|
||||
* Copyright (c) 2009, Fabien Thomas
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_PL_CALLTREE_H_
|
||||
#define _PMCSTAT_PL_CALLTREE_H_
|
||||
|
||||
/* Function prototypes */
|
||||
int pmcpl_ct_init(void);
|
||||
void pmcpl_ct_shutdown(FILE *mf);
|
||||
void pmcpl_ct_process(
|
||||
struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
|
||||
int pmcpl_ct_topkeypress(int c, WINDOW *w);
|
||||
void pmcpl_ct_topdisplay(void);
|
||||
int pmcpl_ct_configure(char *opt);
|
||||
|
||||
#endif /* _PMCSTAT_PL_CALLTREE_H_ */
|
533
usr.sbin/pmcstat/pmcpl_gprof.c
Normal file
533
usr.sbin/pmcstat/pmcpl_gprof.c
Normal file
@ -0,0 +1,533 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* Copyright (c) 2009, Fabien Thomas
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Transform a hwpmc(4) log into human readable form, and into
|
||||
* gprof(1) compatible profiles.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/gmon.h>
|
||||
#include <sys/imgact_aout.h>
|
||||
#include <sys/imgact_elf.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/pmc.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <curses.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gelf.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <pmc.h>
|
||||
#include <pmclog.h>
|
||||
#include <sysexits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pmcstat.h"
|
||||
#include "pmcstat_log.h"
|
||||
#include "pmcpl_callgraph.h"
|
||||
#include "pmcpl_gprof.h"
|
||||
|
||||
/*
|
||||
* struct pmcstat_gmonfile tracks a given 'gmon.out' file. These
|
||||
* files are mmap()'ed in as needed.
|
||||
*/
|
||||
|
||||
struct pmcstat_gmonfile {
|
||||
LIST_ENTRY(pmcstat_gmonfile) pgf_next; /* list of entries */
|
||||
int pgf_overflow; /* whether a count overflowed */
|
||||
pmc_id_t pgf_pmcid; /* id of the associated pmc */
|
||||
size_t pgf_nbuckets; /* #buckets in this gmon.out */
|
||||
unsigned int pgf_nsamples; /* #samples in this gmon.out */
|
||||
pmcstat_interned_string pgf_name; /* pathname of gmon.out file */
|
||||
size_t pgf_ndatabytes; /* number of bytes mapped */
|
||||
void *pgf_gmondata; /* pointer to mmap'ed data */
|
||||
FILE *pgf_file; /* used when writing gmon arcs */
|
||||
};
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
|
||||
static void pmcstat_gmon_create_file(struct pmcstat_gmonfile *_pgf,
|
||||
struct pmcstat_image *_image);
|
||||
static pmcstat_interned_string pmcstat_gmon_create_name(const char *_sd,
|
||||
struct pmcstat_image *_img, pmc_id_t _pmcid);
|
||||
static void pmcstat_gmon_map_file(struct pmcstat_gmonfile *_pgf);
|
||||
static void pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *_pgf);
|
||||
|
||||
static struct pmcstat_gmonfile *pmcstat_image_find_gmonfile(struct
|
||||
pmcstat_image *_i, pmc_id_t _id);
|
||||
|
||||
/*
|
||||
* Create a gmon.out file and size it.
|
||||
*/
|
||||
|
||||
static void
|
||||
pmcstat_gmon_create_file(struct pmcstat_gmonfile *pgf,
|
||||
struct pmcstat_image *image)
|
||||
{
|
||||
int fd;
|
||||
size_t count;
|
||||
struct gmonhdr gm;
|
||||
const char *pathname;
|
||||
char buffer[DEFAULT_BUFFER_SIZE];
|
||||
|
||||
pathname = pmcstat_string_unintern(pgf->pgf_name);
|
||||
if ((fd = open(pathname, O_RDWR|O_NOFOLLOW|O_CREAT,
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot open \"%s\"", pathname);
|
||||
|
||||
gm.lpc = image->pi_start;
|
||||
gm.hpc = image->pi_end;
|
||||
gm.ncnt = (pgf->pgf_nbuckets * sizeof(HISTCOUNTER)) +
|
||||
sizeof(struct gmonhdr);
|
||||
gm.version = GMONVERSION;
|
||||
gm.profrate = 0; /* use ticks */
|
||||
gm.histcounter_type = 0; /* compatibility with moncontrol() */
|
||||
gm.spare[0] = gm.spare[1] = 0;
|
||||
|
||||
/* Write out the gmon header */
|
||||
if (write(fd, &gm, sizeof(gm)) < 0)
|
||||
goto error;
|
||||
|
||||
/* Zero fill the samples[] array */
|
||||
(void) memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
count = pgf->pgf_ndatabytes - sizeof(struct gmonhdr);
|
||||
while (count > sizeof(buffer)) {
|
||||
if (write(fd, &buffer, sizeof(buffer)) < 0)
|
||||
goto error;
|
||||
count -= sizeof(buffer);
|
||||
}
|
||||
|
||||
if (write(fd, &buffer, count) < 0)
|
||||
goto error;
|
||||
|
||||
(void) close(fd);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
err(EX_OSERR, "ERROR: Cannot write \"%s\"", pathname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the full pathname of a gmon.out file for a given
|
||||
* (image,pmcid) combination. Return the interned string.
|
||||
*/
|
||||
|
||||
pmcstat_interned_string
|
||||
pmcstat_gmon_create_name(const char *samplesdir, struct pmcstat_image *image,
|
||||
pmc_id_t pmcid)
|
||||
{
|
||||
const char *pmcname;
|
||||
char fullpath[PATH_MAX];
|
||||
|
||||
pmcname = pmcstat_pmcid_to_name(pmcid);
|
||||
|
||||
(void) snprintf(fullpath, sizeof(fullpath),
|
||||
"%s/%s/%s", samplesdir, pmcname,
|
||||
pmcstat_string_unintern(image->pi_samplename));
|
||||
|
||||
return (pmcstat_string_intern(fullpath));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Mmap in a gmon.out file for processing.
|
||||
*/
|
||||
|
||||
static void
|
||||
pmcstat_gmon_map_file(struct pmcstat_gmonfile *pgf)
|
||||
{
|
||||
int fd;
|
||||
const char *pathname;
|
||||
|
||||
pathname = pmcstat_string_unintern(pgf->pgf_name);
|
||||
|
||||
/* the gmon.out file must already exist */
|
||||
if ((fd = open(pathname, O_RDWR | O_NOFOLLOW, 0)) < 0)
|
||||
err(EX_OSERR, "ERROR: cannot open \"%s\"", pathname);
|
||||
|
||||
pgf->pgf_gmondata = mmap(NULL, pgf->pgf_ndatabytes,
|
||||
PROT_READ|PROT_WRITE, MAP_NOSYNC|MAP_SHARED, fd, 0);
|
||||
|
||||
if (pgf->pgf_gmondata == MAP_FAILED)
|
||||
err(EX_OSERR, "ERROR: cannot map \"%s\"", pathname);
|
||||
|
||||
(void) close(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unmap a gmon.out file after sync'ing its data to disk.
|
||||
*/
|
||||
|
||||
static void
|
||||
pmcstat_gmon_unmap_file(struct pmcstat_gmonfile *pgf)
|
||||
{
|
||||
(void) msync(pgf->pgf_gmondata, pgf->pgf_ndatabytes,
|
||||
MS_SYNC);
|
||||
(void) munmap(pgf->pgf_gmondata, pgf->pgf_ndatabytes);
|
||||
pgf->pgf_gmondata = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
pmcstat_gmon_append_arc(struct pmcstat_image *image, pmc_id_t pmcid,
|
||||
uintptr_t rawfrom, uintptr_t rawto, uint32_t count)
|
||||
{
|
||||
struct rawarc arc; /* from <sys/gmon.h> */
|
||||
const char *pathname;
|
||||
struct pmcstat_gmonfile *pgf;
|
||||
|
||||
if ((pgf = pmcstat_image_find_gmonfile(image, pmcid)) == NULL)
|
||||
return;
|
||||
|
||||
if (pgf->pgf_file == NULL) {
|
||||
pathname = pmcstat_string_unintern(pgf->pgf_name);
|
||||
if ((pgf->pgf_file = fopen(pathname, "a")) == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
arc.raw_frompc = rawfrom + image->pi_vaddr;
|
||||
arc.raw_selfpc = rawto + image->pi_vaddr;
|
||||
arc.raw_count = count;
|
||||
|
||||
(void) fwrite(&arc, sizeof(arc), 1, pgf->pgf_file);
|
||||
|
||||
}
|
||||
|
||||
static struct pmcstat_gmonfile *
|
||||
pmcstat_image_find_gmonfile(struct pmcstat_image *image, pmc_id_t pmcid)
|
||||
{
|
||||
struct pmcstat_gmonfile *pgf;
|
||||
LIST_FOREACH(pgf, &image->pi_gmlist, pgf_next)
|
||||
if (pgf->pgf_pmcid == pmcid)
|
||||
return (pgf);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
pmcstat_cgnode_do_gmon_arcs(struct pmcstat_cgnode *cg, pmc_id_t pmcid)
|
||||
{
|
||||
struct pmcstat_cgnode *cgc;
|
||||
|
||||
/*
|
||||
* Look for child nodes that belong to the same image.
|
||||
*/
|
||||
|
||||
LIST_FOREACH(cgc, &cg->pcg_children, pcg_sibling) {
|
||||
if (cgc->pcg_image == cg->pcg_image)
|
||||
pmcstat_gmon_append_arc(cg->pcg_image, pmcid,
|
||||
cgc->pcg_func, cg->pcg_func, cgc->pcg_count);
|
||||
if (cgc->pcg_nchildren > 0)
|
||||
pmcstat_cgnode_do_gmon_arcs(cgc, pmcid);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmc_id_t pmcid)
|
||||
{
|
||||
int n;
|
||||
struct pmcstat_cgnode_hash *pch;
|
||||
|
||||
for (n = 0; n < PMCSTAT_NHASH; n++)
|
||||
LIST_FOREACH(pch, &pmcstat_cgnode_hash[n], pch_next)
|
||||
if (pch->pch_pmcid == pmcid &&
|
||||
pch->pch_cgnode->pcg_nchildren > 1)
|
||||
pmcstat_cgnode_do_gmon_arcs(pch->pch_cgnode,
|
||||
pmcid);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pmcstat_callgraph_do_gmon_arcs(void)
|
||||
{
|
||||
struct pmcstat_pmcrecord *pmcr;
|
||||
|
||||
LIST_FOREACH(pmcr, &pmcstat_pmcs, pr_next)
|
||||
pmcstat_callgraph_do_gmon_arcs_for_pmcid(pmcr->pr_pmcid);
|
||||
}
|
||||
|
||||
void
|
||||
pmcpl_gmon_initimage(struct pmcstat_image *pi)
|
||||
{
|
||||
int count, nlen;
|
||||
char *sn;
|
||||
char name[NAME_MAX];
|
||||
|
||||
/*
|
||||
* Look for a suitable name for the sample files associated
|
||||
* with this image: if `basename(path)`+".gmon" is available,
|
||||
* we use that, otherwise we try iterating through
|
||||
* `basename(path)`+ "~" + NNN + ".gmon" till we get a free
|
||||
* entry.
|
||||
*/
|
||||
if ((sn = basename(pmcstat_string_unintern(pi->pi_execpath))) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot process \"%s\"",
|
||||
pmcstat_string_unintern(pi->pi_execpath));
|
||||
|
||||
nlen = strlen(sn);
|
||||
nlen = min(nlen, (int) (sizeof(name) - sizeof(".gmon")));
|
||||
|
||||
snprintf(name, sizeof(name), "%.*s.gmon", nlen, sn);
|
||||
|
||||
/* try use the unabridged name first */
|
||||
if (pmcstat_string_lookup(name) == NULL)
|
||||
pi->pi_samplename = pmcstat_string_intern(name);
|
||||
else {
|
||||
/*
|
||||
* Otherwise use a prefix from the original name and
|
||||
* upto 3 digits.
|
||||
*/
|
||||
nlen = strlen(sn);
|
||||
nlen = min(nlen, (int) (sizeof(name)-sizeof("~NNN.gmon")));
|
||||
count = 0;
|
||||
do {
|
||||
if (++count > 999)
|
||||
errx(EX_CANTCREAT, "ERROR: cannot create a "
|
||||
"gmon file for \"%s\"", name);
|
||||
snprintf(name, sizeof(name), "%.*s~%3.3d.gmon",
|
||||
nlen, sn, count);
|
||||
if (pmcstat_string_lookup(name) == NULL) {
|
||||
pi->pi_samplename =
|
||||
pmcstat_string_intern(name);
|
||||
count = 0;
|
||||
}
|
||||
} while (count > 0);
|
||||
}
|
||||
|
||||
LIST_INIT(&pi->pi_gmlist);
|
||||
}
|
||||
|
||||
void
|
||||
pmcpl_gmon_shutdownimage(struct pmcstat_image *pi)
|
||||
{
|
||||
struct pmcstat_gmonfile *pgf, *pgftmp;
|
||||
|
||||
LIST_FOREACH_SAFE(pgf, &pi->pi_gmlist, pgf_next, pgftmp) {
|
||||
if (pgf->pgf_file)
|
||||
(void) fclose(pgf->pgf_file);
|
||||
LIST_REMOVE(pgf, pgf_next);
|
||||
free(pgf);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
pmcpl_gmon_newpmc(pmcstat_interned_string ps, struct pmcstat_pmcrecord *pr)
|
||||
{
|
||||
struct stat st;
|
||||
char fullpath[PATH_MAX];
|
||||
|
||||
(void) pr;
|
||||
|
||||
/*
|
||||
* Create the appropriate directory to hold gmon.out files.
|
||||
*/
|
||||
|
||||
(void) snprintf(fullpath, sizeof(fullpath), "%s/%s", args.pa_samplesdir,
|
||||
pmcstat_string_unintern(ps));
|
||||
|
||||
/* If the path name exists, it should be a directory */
|
||||
if (stat(fullpath, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
return;
|
||||
|
||||
if (mkdir(fullpath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot create directory \"%s\"",
|
||||
fullpath);
|
||||
}
|
||||
|
||||
/*
|
||||
* Increment the bucket in the gmon.out file corresponding to 'pmcid'
|
||||
* and 'pc'.
|
||||
*/
|
||||
|
||||
void
|
||||
pmcpl_gmon_process(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu)
|
||||
{
|
||||
struct pmcstat_pcmap *map;
|
||||
struct pmcstat_image *image;
|
||||
struct pmcstat_gmonfile *pgf;
|
||||
uintfptr_t bucket;
|
||||
HISTCOUNTER *hc;
|
||||
pmc_id_t pmcid;
|
||||
|
||||
(void) nsamples; (void) usermode; (void) cpu;
|
||||
|
||||
map = pmcstat_process_find_map(usermode ? pp : pmcstat_kernproc, cc[0]);
|
||||
if (map == NULL) {
|
||||
/* Unknown offset. */
|
||||
pmcstat_stats.ps_samples_unknown_offset++;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(cc[0] >= map->ppm_lowpc && cc[0] < map->ppm_highpc);
|
||||
|
||||
image = map->ppm_image;
|
||||
pmcid = pmcr->pr_pmcid;
|
||||
|
||||
/*
|
||||
* If this is the first time we are seeing a sample for
|
||||
* this executable image, try determine its parameters.
|
||||
*/
|
||||
if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN)
|
||||
pmcstat_image_determine_type(image);
|
||||
|
||||
assert(image->pi_type != PMCSTAT_IMAGE_UNKNOWN);
|
||||
|
||||
/* Ignore samples in images that we know nothing about. */
|
||||
if (image->pi_type == PMCSTAT_IMAGE_INDETERMINABLE) {
|
||||
pmcstat_stats.ps_samples_indeterminable++;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the gmon file corresponding to 'pmcid', creating it if
|
||||
* needed.
|
||||
*/
|
||||
pgf = pmcstat_image_find_gmonfile(image, pmcid);
|
||||
if (pgf == NULL) {
|
||||
if ((pgf = calloc(1, sizeof(*pgf))) == NULL)
|
||||
err(EX_OSERR, "ERROR:");
|
||||
|
||||
pgf->pgf_gmondata = NULL; /* mark as unmapped */
|
||||
pgf->pgf_name = pmcstat_gmon_create_name(args.pa_samplesdir,
|
||||
image, pmcid);
|
||||
pgf->pgf_pmcid = pmcid;
|
||||
assert(image->pi_end > image->pi_start);
|
||||
pgf->pgf_nbuckets = (image->pi_end - image->pi_start) /
|
||||
FUNCTION_ALIGNMENT; /* see <machine/profile.h> */
|
||||
pgf->pgf_ndatabytes = sizeof(struct gmonhdr) +
|
||||
pgf->pgf_nbuckets * sizeof(HISTCOUNTER);
|
||||
pgf->pgf_nsamples = 0;
|
||||
pgf->pgf_file = NULL;
|
||||
|
||||
pmcstat_gmon_create_file(pgf, image);
|
||||
|
||||
LIST_INSERT_HEAD(&image->pi_gmlist, pgf, pgf_next);
|
||||
}
|
||||
|
||||
/*
|
||||
* Map the gmon file in if needed. It may have been mapped
|
||||
* out under memory pressure.
|
||||
*/
|
||||
if (pgf->pgf_gmondata == NULL)
|
||||
pmcstat_gmon_map_file(pgf);
|
||||
|
||||
assert(pgf->pgf_gmondata != NULL);
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
bucket = (cc[0] - map->ppm_lowpc) / FUNCTION_ALIGNMENT;
|
||||
|
||||
assert(bucket < pgf->pgf_nbuckets);
|
||||
|
||||
hc = (HISTCOUNTER *) ((uintptr_t) pgf->pgf_gmondata +
|
||||
sizeof(struct gmonhdr));
|
||||
|
||||
/* saturating add */
|
||||
if (hc[bucket] < 0xFFFFU) /* XXX tie this to sizeof(HISTCOUNTER) */
|
||||
hc[bucket]++;
|
||||
else /* mark that an overflow occurred */
|
||||
pgf->pgf_overflow = 1;
|
||||
|
||||
pgf->pgf_nsamples++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown module.
|
||||
*/
|
||||
|
||||
void
|
||||
pmcpl_gmon_shutdown(FILE *mf)
|
||||
{
|
||||
int i;
|
||||
struct pmcstat_gmonfile *pgf;
|
||||
struct pmcstat_image *pi;
|
||||
|
||||
/*
|
||||
* Sync back all gprof flat profile data.
|
||||
*/
|
||||
for (i = 0; i < PMCSTAT_NHASH; i++) {
|
||||
LIST_FOREACH(pi, &pmcstat_image_hash[i], pi_next) {
|
||||
if (mf)
|
||||
(void) fprintf(mf, " \"%s\" => \"%s\"",
|
||||
pmcstat_string_unintern(pi->pi_execpath),
|
||||
pmcstat_string_unintern(
|
||||
pi->pi_samplename));
|
||||
|
||||
/* flush gmon.out data to disk */
|
||||
LIST_FOREACH(pgf, &pi->pi_gmlist, pgf_next) {
|
||||
pmcstat_gmon_unmap_file(pgf);
|
||||
if (mf)
|
||||
(void) fprintf(mf, " %s/%d",
|
||||
pmcstat_pmcid_to_name(
|
||||
pgf->pgf_pmcid),
|
||||
pgf->pgf_nsamples);
|
||||
if (pgf->pgf_overflow && args.pa_verbosity >= 1)
|
||||
warnx("WARNING: profile \"%s\" "
|
||||
"overflowed.",
|
||||
pmcstat_string_unintern(
|
||||
pgf->pgf_name));
|
||||
}
|
||||
|
||||
if (mf)
|
||||
(void) fprintf(mf, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute arcs and add these to the gprof files.
|
||||
*/
|
||||
if (args.pa_flags & FLAG_DO_GPROF && args.pa_graphdepth > 1)
|
||||
pmcstat_callgraph_do_gmon_arcs();
|
||||
}
|
47
usr.sbin/pmcstat/pmcpl_gprof.h
Normal file
47
usr.sbin/pmcstat/pmcpl_gprof.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* Copyright (c) 2009, Fabien Thomas
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_PL_GPROF_H_
|
||||
#define _PMCSTAT_PL_GPROF_H_
|
||||
|
||||
/* Function prototypes */
|
||||
void pmcpl_gmon_shutdown(FILE *mf);
|
||||
void pmcpl_gmon_process(
|
||||
struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr,
|
||||
uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu);
|
||||
void pmcpl_gmon_initimage(struct pmcstat_image *pi);
|
||||
void pmcpl_gmon_shutdownimage(struct pmcstat_image *pi);
|
||||
void pmcpl_gmon_newpmc(pmcstat_interned_string ps,
|
||||
struct pmcstat_pmcrecord *pr);
|
||||
|
||||
#endif /* _PMCSTAT_PL_GPROF_H_ */
|
@ -36,6 +36,7 @@
|
||||
.Op Fl C
|
||||
.Op Fl D Ar pathname
|
||||
.Op Fl E
|
||||
.Op Fl F Ar pathname
|
||||
.Op Fl G Ar pathname
|
||||
.Op Fl M Ar mapfilename
|
||||
.Op Fl N
|
||||
@ -43,9 +44,11 @@
|
||||
.Op Fl P Ar event-spec
|
||||
.Op Fl R Ar logfilename
|
||||
.Op Fl S Ar event-spec
|
||||
.Op Fl T
|
||||
.Op Fl W
|
||||
.Op Fl c Ar cpu-spec
|
||||
.Op Fl d
|
||||
.Op Fl f Ar pluginopt
|
||||
.Op Fl g
|
||||
.Op Fl k Ar kerneldir
|
||||
.Op Fl m Ar pathname
|
||||
@ -129,6 +132,16 @@ complex pipeline of processes when used in conjunction with the
|
||||
.Fl d
|
||||
option.
|
||||
The default is to not to enable per-process tracking.
|
||||
.It Fl F Ar pathname
|
||||
Print calltree (Kcachegrind) information to file
|
||||
.Ar pathname .
|
||||
If argument
|
||||
.Ar pathname
|
||||
is a
|
||||
.Dq Li -
|
||||
this information is sent to the output file specified by the
|
||||
.Fl o
|
||||
option.
|
||||
.It Fl G Ar pathname
|
||||
Print callchain information to file
|
||||
.Ar pathname .
|
||||
@ -195,6 +208,12 @@ Perform offline analysis using sampling data in file
|
||||
Allocate a system mode sampling PMC measuring hardware events
|
||||
specified in
|
||||
.Ar event-spec .
|
||||
.It Fl T
|
||||
Use a top like mode for sampling PMCs. The following hotkeys
|
||||
can be used: 'c+a' switch to accumulative mode, 'c+d' switch
|
||||
to delta mode, 'm' merge PMCs, 'n' change view, 'p' show next
|
||||
PMC, ' ' pause, 'q' quit. calltree only: 'f' cost under threshold
|
||||
is seen as a dot.
|
||||
.It Fl W
|
||||
Toggle logging the incremental counts seen by the threads of a
|
||||
tracked process each time they are scheduled on a CPU.
|
||||
@ -218,6 +237,12 @@ Toggle between process mode PMCs measuring events for the target
|
||||
process' current and future children or only measuring events for
|
||||
the target process.
|
||||
The default is to measure events for the target process alone.
|
||||
.It Fl f Ar pluginopt
|
||||
Pass option string to the active plugin.
|
||||
.br
|
||||
threshold=<float> do not display cost under specified value (Top).
|
||||
.br
|
||||
skiplink=0|1 replace node with cost under threshold by a dot (Top).
|
||||
.It Fl g
|
||||
Produce profiles in a format compatible with
|
||||
.Xr gprof 1 .
|
||||
@ -286,7 +311,8 @@ regular expression for selecting processes based on their command names.
|
||||
.It Fl v
|
||||
Increase verbosity.
|
||||
.It Fl w Ar secs
|
||||
Print the values of all counting mode PMCs every
|
||||
Print the values of all counting mode PMCs or sampling mode PMCs
|
||||
for top mode every
|
||||
.Ar secs
|
||||
seconds.
|
||||
The argument
|
||||
|
@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <curses.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@ -106,13 +107,15 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
int pmcstat_interrupt = 0;
|
||||
int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT;
|
||||
int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH;
|
||||
int pmcstat_sockpair[NSOCKPAIRFD];
|
||||
int pmcstat_kq;
|
||||
kvm_t *pmcstat_kvm;
|
||||
struct kinfo_proc *pmcstat_plist;
|
||||
struct pmcstat_args args;
|
||||
|
||||
void
|
||||
pmcstat_attach_pmcs(struct pmcstat_args *a)
|
||||
pmcstat_attach_pmcs(void)
|
||||
{
|
||||
struct pmcstat_ev *ev;
|
||||
struct pmcstat_target *pt;
|
||||
@ -120,10 +123,10 @@ pmcstat_attach_pmcs(struct pmcstat_args *a)
|
||||
|
||||
/* Attach all process PMCs to target processes. */
|
||||
count = 0;
|
||||
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
|
||||
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
|
||||
if (PMC_IS_SYSTEM_MODE(ev->ev_mode))
|
||||
continue;
|
||||
SLIST_FOREACH(pt, &a->pa_targets, pt_next)
|
||||
SLIST_FOREACH(pt, &args.pa_targets, pt_next)
|
||||
if (pmc_attach(ev->ev_pmcid, pt->pt_pid) == 0)
|
||||
count++;
|
||||
else if (errno != ESRCH)
|
||||
@ -138,12 +141,12 @@ pmcstat_attach_pmcs(struct pmcstat_args *a)
|
||||
|
||||
|
||||
void
|
||||
pmcstat_cleanup(struct pmcstat_args *a)
|
||||
pmcstat_cleanup(void)
|
||||
{
|
||||
struct pmcstat_ev *ev, *tmp;
|
||||
|
||||
/* release allocated PMCs. */
|
||||
STAILQ_FOREACH_SAFE(ev, &a->pa_events, ev_next, tmp)
|
||||
STAILQ_FOREACH_SAFE(ev, &args.pa_events, ev_next, tmp)
|
||||
if (ev->ev_pmcid != PMC_ID_INVALID) {
|
||||
if (pmc_stop(ev->ev_pmcid) < 0)
|
||||
err(EX_OSERR, "ERROR: cannot stop pmc 0x%x "
|
||||
@ -153,25 +156,25 @@ pmcstat_cleanup(struct pmcstat_args *a)
|
||||
"0x%x \"%s\"", ev->ev_pmcid, ev->ev_name);
|
||||
free(ev->ev_name);
|
||||
free(ev->ev_spec);
|
||||
STAILQ_REMOVE(&a->pa_events, ev, pmcstat_ev, ev_next);
|
||||
STAILQ_REMOVE(&args.pa_events, ev, pmcstat_ev, ev_next);
|
||||
free(ev);
|
||||
}
|
||||
|
||||
/* de-configure the log file if present. */
|
||||
if (a->pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
|
||||
if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
|
||||
(void) pmc_configure_logfile(-1);
|
||||
|
||||
if (a->pa_logparser) {
|
||||
pmclog_close(a->pa_logparser);
|
||||
a->pa_logparser = NULL;
|
||||
if (args.pa_logparser) {
|
||||
pmclog_close(args.pa_logparser);
|
||||
args.pa_logparser = NULL;
|
||||
}
|
||||
|
||||
if (a->pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
|
||||
pmcstat_shutdown_logging(a);
|
||||
if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE))
|
||||
pmcstat_shutdown_logging();
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_clone_event_descriptor(struct pmcstat_args *a, struct pmcstat_ev *ev,
|
||||
pmcstat_clone_event_descriptor(struct pmcstat_ev *ev,
|
||||
uint32_t cpumask)
|
||||
{
|
||||
int cpu;
|
||||
@ -194,14 +197,14 @@ pmcstat_clone_event_descriptor(struct pmcstat_args *a, struct pmcstat_ev *ev,
|
||||
ev_clone->ev_saved = ev->ev_saved;
|
||||
ev_clone->ev_spec = strdup(ev->ev_spec);
|
||||
|
||||
STAILQ_INSERT_TAIL(&a->pa_events, ev_clone, ev_next);
|
||||
STAILQ_INSERT_TAIL(&args.pa_events, ev_clone, ev_next);
|
||||
|
||||
cpumask &= ~(1 << cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_create_process(struct pmcstat_args *a)
|
||||
pmcstat_create_process(void)
|
||||
{
|
||||
char token;
|
||||
pid_t pid;
|
||||
@ -229,10 +232,10 @@ pmcstat_create_process(struct pmcstat_args *a)
|
||||
(void) close(pmcstat_sockpair[CHILDSOCKET]);
|
||||
|
||||
/* exec() the program requested */
|
||||
execvp(*a->pa_argv, a->pa_argv);
|
||||
execvp(*args.pa_argv, args.pa_argv);
|
||||
/* and if that fails, notify the parent */
|
||||
kill(getppid(), SIGCHLD);
|
||||
err(EX_OSERR, "ERROR: execvp \"%s\" failed", *a->pa_argv);
|
||||
err(EX_OSERR, "ERROR: execvp \"%s\" failed", *args.pa_argv);
|
||||
/*NOTREACHED*/
|
||||
|
||||
default: /* parent */
|
||||
@ -250,7 +253,7 @@ pmcstat_create_process(struct pmcstat_args *a)
|
||||
errx(EX_SOFTWARE, "ERROR: Out of memory.");
|
||||
|
||||
pt->pt_pid = pid;
|
||||
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
|
||||
SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
|
||||
|
||||
/* Wait for the child to signal that its ready to go. */
|
||||
if (read(pmcstat_sockpair[PARENTSOCKET], &token, 1) < 0)
|
||||
@ -260,7 +263,7 @@ pmcstat_create_process(struct pmcstat_args *a)
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_find_targets(struct pmcstat_args *a, const char *spec)
|
||||
pmcstat_find_targets(const char *spec)
|
||||
{
|
||||
int n, nproc, pid, rv;
|
||||
struct pmcstat_target *pt;
|
||||
@ -275,7 +278,7 @@ pmcstat_find_targets(struct pmcstat_args *a, const char *spec)
|
||||
if ((pt = malloc(sizeof(*pt))) == NULL)
|
||||
goto outofmemory;
|
||||
pt->pt_pid = pid;
|
||||
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
|
||||
SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -302,7 +305,7 @@ pmcstat_find_targets(struct pmcstat_args *a, const char *spec)
|
||||
if ((pt = malloc(sizeof(*pt))) == NULL)
|
||||
goto outofmemory;
|
||||
pt->pt_pid = kp->ki_pid;
|
||||
SLIST_INSERT_HEAD(&a->pa_targets, pt, pt_next);
|
||||
SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next);
|
||||
} else if (rv != REG_NOMATCH) {
|
||||
regerror(rv, ®, errbuf, sizeof(errbuf));
|
||||
errx(EX_SOFTWARE, "ERROR: Regex evalation failed: %s",
|
||||
@ -343,17 +346,17 @@ pmcstat_get_cpumask(const char *cpuspec)
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_kill_process(struct pmcstat_args *a)
|
||||
pmcstat_kill_process(void)
|
||||
{
|
||||
struct pmcstat_target *pt;
|
||||
|
||||
assert(a->pa_flags & FLAG_HAS_COMMANDLINE);
|
||||
assert(args.pa_flags & FLAG_HAS_COMMANDLINE);
|
||||
|
||||
/*
|
||||
* If a command line was specified, it would be the very first
|
||||
* in the list, before any other processes specified by -t.
|
||||
*/
|
||||
pt = SLIST_FIRST(&a->pa_targets);
|
||||
pt = SLIST_FIRST(&args.pa_targets);
|
||||
assert(pt != NULL);
|
||||
|
||||
if (kill(pt->pt_pid, SIGINT) != 0)
|
||||
@ -361,7 +364,7 @@ pmcstat_kill_process(struct pmcstat_args *a)
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_start_pmcs(struct pmcstat_args *a)
|
||||
pmcstat_start_pmcs(void)
|
||||
{
|
||||
struct pmcstat_ev *ev;
|
||||
|
||||
@ -372,7 +375,7 @@ pmcstat_start_pmcs(struct pmcstat_args *a)
|
||||
if (pmc_start(ev->ev_pmcid) < 0) {
|
||||
warn("ERROR: Cannot start pmc 0x%x \"%s\"",
|
||||
ev->ev_pmcid, ev->ev_name);
|
||||
pmcstat_cleanup(a);
|
||||
pmcstat_cleanup();
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
}
|
||||
@ -380,37 +383,37 @@ pmcstat_start_pmcs(struct pmcstat_args *a)
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_print_headers(struct pmcstat_args *a)
|
||||
pmcstat_print_headers(void)
|
||||
{
|
||||
struct pmcstat_ev *ev;
|
||||
int c, w;
|
||||
|
||||
(void) fprintf(a->pa_printfile, PRINT_HEADER_PREFIX);
|
||||
(void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX);
|
||||
|
||||
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
|
||||
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
|
||||
if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
|
||||
continue;
|
||||
|
||||
c = PMC_IS_SYSTEM_MODE(ev->ev_mode) ? 's' : 'p';
|
||||
|
||||
if (ev->ev_fieldskip != 0)
|
||||
(void) fprintf(a->pa_printfile, "%*s",
|
||||
(void) fprintf(args.pa_printfile, "%*s",
|
||||
ev->ev_fieldskip, "");
|
||||
w = ev->ev_fieldwidth - ev->ev_fieldskip - 2;
|
||||
|
||||
if (c == 's')
|
||||
(void) fprintf(a->pa_printfile, "s/%02d/%-*s ",
|
||||
(void) fprintf(args.pa_printfile, "s/%02d/%-*s ",
|
||||
ev->ev_cpu, w-3, ev->ev_name);
|
||||
else
|
||||
(void) fprintf(a->pa_printfile, "p/%*s ", w,
|
||||
(void) fprintf(args.pa_printfile, "p/%*s ", w,
|
||||
ev->ev_name);
|
||||
}
|
||||
|
||||
(void) fflush(a->pa_printfile);
|
||||
(void) fflush(args.pa_printfile);
|
||||
}
|
||||
|
||||
void
|
||||
pmcstat_print_counters(struct pmcstat_args *a)
|
||||
pmcstat_print_counters(void)
|
||||
{
|
||||
int extra_width;
|
||||
struct pmcstat_ev *ev;
|
||||
@ -418,7 +421,7 @@ pmcstat_print_counters(struct pmcstat_args *a)
|
||||
|
||||
extra_width = sizeof(PRINT_HEADER_PREFIX) - 1;
|
||||
|
||||
STAILQ_FOREACH(ev, &a->pa_events, ev_next) {
|
||||
STAILQ_FOREACH(ev, &args.pa_events, ev_next) {
|
||||
|
||||
/* skip sampling mode counters */
|
||||
if (PMC_IS_SAMPLING_MODE(ev->ev_mode))
|
||||
@ -428,7 +431,7 @@ pmcstat_print_counters(struct pmcstat_args *a)
|
||||
err(EX_OSERR, "ERROR: Cannot read pmc "
|
||||
"\"%s\"", ev->ev_name);
|
||||
|
||||
(void) fprintf(a->pa_printfile, "%*ju ",
|
||||
(void) fprintf(args.pa_printfile, "%*ju ",
|
||||
ev->ev_fieldwidth + extra_width,
|
||||
(uintmax_t) ev->ev_cumulative ? value :
|
||||
(value - ev->ev_saved));
|
||||
@ -438,7 +441,7 @@ pmcstat_print_counters(struct pmcstat_args *a)
|
||||
extra_width = 0;
|
||||
}
|
||||
|
||||
(void) fflush(a->pa_printfile);
|
||||
(void) fflush(args.pa_printfile);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -446,20 +449,20 @@ pmcstat_print_counters(struct pmcstat_args *a)
|
||||
*/
|
||||
|
||||
void
|
||||
pmcstat_print_pmcs(struct pmcstat_args *a)
|
||||
pmcstat_print_pmcs(void)
|
||||
{
|
||||
static int linecount = 0;
|
||||
|
||||
/* check if we need to print a header line */
|
||||
if (++linecount > pmcstat_displayheight) {
|
||||
(void) fprintf(a->pa_printfile, "\n");
|
||||
(void) fprintf(args.pa_printfile, "\n");
|
||||
linecount = 1;
|
||||
}
|
||||
if (linecount == 1)
|
||||
pmcstat_print_headers(a);
|
||||
(void) fprintf(a->pa_printfile, "\n");
|
||||
pmcstat_print_headers();
|
||||
(void) fprintf(args.pa_printfile, "\n");
|
||||
|
||||
pmcstat_print_counters(a);
|
||||
pmcstat_print_counters();
|
||||
|
||||
return;
|
||||
}
|
||||
@ -493,6 +496,8 @@ pmcstat_show_usage(void)
|
||||
"\t -C\t\t (toggle) show cumulative counts\n"
|
||||
"\t -D path\t create profiles in directory \"path\"\n"
|
||||
"\t -E\t\t (toggle) show counts at process exit\n"
|
||||
"\t -F file\t write a system-wide callgraph (Kcachegrind format)"
|
||||
" to \"file\"\n"
|
||||
"\t -G file\t write a system-wide callgraph to \"file\"\n"
|
||||
"\t -M file\t print executable/gmon file map to \"file\"\n"
|
||||
"\t -N\t\t (toggle) capture callchains\n"
|
||||
@ -500,9 +505,11 @@ pmcstat_show_usage(void)
|
||||
"\t -P spec\t allocate a process-private sampling PMC\n"
|
||||
"\t -R file\t read events from \"file\"\n"
|
||||
"\t -S spec\t allocate a system-wide sampling PMC\n"
|
||||
"\t -T\t\t start in top mode\n"
|
||||
"\t -W\t\t (toggle) show counts per context switch\n"
|
||||
"\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n"
|
||||
"\t -d\t\t (toggle) track descendants\n"
|
||||
"\t -f spec\t pass \"spec\" to as plugin option\n"
|
||||
"\t -g\t\t produce gprof(1) compatible profiles\n"
|
||||
"\t -k dir\t\t set the path to the kernel\n"
|
||||
"\t -n rate\t set sampling rate\n"
|
||||
@ -519,6 +526,24 @@ pmcstat_show_usage(void)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* At exit handler for top mode
|
||||
*/
|
||||
|
||||
void
|
||||
pmcstat_topexit(void)
|
||||
{
|
||||
if (!args.pa_toptty)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Shutdown ncurses.
|
||||
*/
|
||||
clrtoeol();
|
||||
refresh();
|
||||
endwin();
|
||||
}
|
||||
|
||||
/*
|
||||
* Main
|
||||
*/
|
||||
@ -535,6 +560,7 @@ main(int argc, char **argv)
|
||||
int graphdepth;
|
||||
int pipefd[2];
|
||||
int use_cumulative_counts;
|
||||
short cf, cb;
|
||||
uint32_t cpumask;
|
||||
char *end, *tmp;
|
||||
const char *errmsg, *graphfilename;
|
||||
@ -570,6 +596,13 @@ main(int argc, char **argv)
|
||||
args.pa_mapfilename = NULL;
|
||||
args.pa_inputpath = NULL;
|
||||
args.pa_outputpath = NULL;
|
||||
args.pa_pplugin = PMCSTAT_PL_NONE;
|
||||
args.pa_plugin = PMCSTAT_PL_NONE;
|
||||
args.pa_ctdumpinstr = 1;
|
||||
args.pa_topmode = PMCSTAT_TOP_DELTA;
|
||||
args.pa_toptty = 0;
|
||||
args.pa_topcolor = 0;
|
||||
args.pa_mergepmc = 0;
|
||||
STAILQ_INIT(&args.pa_events);
|
||||
SLIST_INIT(&args.pa_targets);
|
||||
bzero(&ds_start, sizeof(ds_start));
|
||||
@ -594,7 +627,7 @@ main(int argc, char **argv)
|
||||
}
|
||||
|
||||
while ((option = getopt(argc, argv,
|
||||
"CD:EG:M:NO:P:R:S:Wc:dgk:m:n:o:p:qr:s:t:vw:z:")) != -1)
|
||||
"CD:EF:G:M:NO:P:R:S:TWc:df:gk:m:n:o:p:qr:s:t:vw:z:")) != -1)
|
||||
switch (option) {
|
||||
case 'C': /* cumulative values */
|
||||
use_cumulative_counts = !use_cumulative_counts;
|
||||
@ -628,13 +661,28 @@ main(int argc, char **argv)
|
||||
args.pa_required |= FLAG_HAS_PROCESS_PMCS;
|
||||
break;
|
||||
|
||||
case 'F': /* produce a system-wide calltree */
|
||||
args.pa_flags |= FLAG_DO_CALLGRAPHS;
|
||||
args.pa_plugin = PMCSTAT_PL_CALLTREE;
|
||||
graphfilename = optarg;
|
||||
break;
|
||||
|
||||
case 'f': /* plugins options */
|
||||
if (args.pa_plugin == PMCSTAT_PL_NONE)
|
||||
err(EX_USAGE, "ERROR: Need -g/-G/-m/-T.");
|
||||
pmcstat_pluginconfigure_log(optarg);
|
||||
break;
|
||||
|
||||
case 'G': /* produce a system-wide callgraph */
|
||||
args.pa_flags |= FLAG_DO_CALLGRAPHS;
|
||||
args.pa_plugin = PMCSTAT_PL_CALLGRAPH;
|
||||
graphfilename = optarg;
|
||||
break;
|
||||
|
||||
case 'g': /* produce gprof compatible profiles */
|
||||
args.pa_flags |= FLAG_DO_GPROF;
|
||||
args.pa_pplugin = PMCSTAT_PL_CALLGRAPH;
|
||||
args.pa_plugin = PMCSTAT_PL_GPROF;
|
||||
break;
|
||||
|
||||
case 'k': /* pathname to the kernel */
|
||||
@ -645,8 +693,9 @@ main(int argc, char **argv)
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
args.pa_flags |= FLAG_WANTS_MAPPINGS;
|
||||
graphfilename = optarg;
|
||||
args.pa_flags |= FLAG_DO_ANNOTATE;
|
||||
args.pa_plugin = PMCSTAT_PL_ANNOTATE;
|
||||
graphfilename = optarg;
|
||||
break;
|
||||
|
||||
case 'E': /* log process exit */
|
||||
@ -732,7 +781,7 @@ main(int argc, char **argv)
|
||||
STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next);
|
||||
|
||||
if (option == 's' || option == 'S')
|
||||
pmcstat_clone_event_descriptor(&args, ev,
|
||||
pmcstat_clone_event_descriptor(ev,
|
||||
cpumask & ~(1 << ev->ev_cpu));
|
||||
|
||||
break;
|
||||
@ -782,12 +831,21 @@ main(int argc, char **argv)
|
||||
break;
|
||||
|
||||
case 't': /* target pid or process name */
|
||||
pmcstat_find_targets(&args, optarg);
|
||||
pmcstat_find_targets(optarg);
|
||||
|
||||
args.pa_flags |= FLAG_HAS_TARGET;
|
||||
args.pa_required |= FLAG_HAS_PROCESS_PMCS;
|
||||
break;
|
||||
|
||||
case 'T': /* top mode */
|
||||
args.pa_flags |= FLAG_DO_TOP;
|
||||
args.pa_plugin = PMCSTAT_PL_CALLGRAPH;
|
||||
args.pa_ctdumpinstr = 0;
|
||||
args.pa_mergepmc = 1;
|
||||
if (args.pa_printfile == stderr)
|
||||
args.pa_printfile = stdout;
|
||||
break;
|
||||
|
||||
case 'v': /* verbose */
|
||||
args.pa_verbosity++;
|
||||
break;
|
||||
@ -798,7 +856,6 @@ main(int argc, char **argv)
|
||||
errx(EX_USAGE, "ERROR: Illegal wait interval "
|
||||
"value \"%s\".", optarg);
|
||||
args.pa_flags |= FLAG_HAS_WAIT_INTERVAL;
|
||||
args.pa_required |= FLAG_HAS_COUNTING_PMCS;
|
||||
args.pa_interval = interval;
|
||||
break;
|
||||
|
||||
@ -833,7 +890,7 @@ main(int argc, char **argv)
|
||||
args.pa_flags |= FLAG_HAS_COMMANDLINE;
|
||||
|
||||
if (args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS |
|
||||
FLAG_WANTS_MAPPINGS))
|
||||
FLAG_DO_ANNOTATE | FLAG_DO_TOP))
|
||||
args.pa_flags |= FLAG_DO_ANALYSIS;
|
||||
|
||||
/*
|
||||
@ -846,11 +903,11 @@ main(int argc, char **argv)
|
||||
"exclusive.");
|
||||
|
||||
/* -m option is allowed with -R only. */
|
||||
if (args.pa_flags & FLAG_WANTS_MAPPINGS && args.pa_inputpath == NULL)
|
||||
if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_inputpath == NULL)
|
||||
errx(EX_USAGE, "ERROR: option -m requires an input file");
|
||||
|
||||
/* -m option is not allowed combined with -g or -G. */
|
||||
if (args.pa_flags & FLAG_WANTS_MAPPINGS &&
|
||||
if (args.pa_flags & FLAG_DO_ANNOTATE &&
|
||||
args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS))
|
||||
errx(EX_USAGE, "ERROR: option -m and -g | -G are mutually "
|
||||
"exclusive");
|
||||
@ -904,7 +961,7 @@ main(int argc, char **argv)
|
||||
/* check for counting mode options without a counting PMC */
|
||||
if ((args.pa_required & FLAG_HAS_COUNTING_PMCS) &&
|
||||
(args.pa_flags & FLAG_HAS_COUNTING_PMCS) == 0)
|
||||
errx(EX_USAGE, "ERROR: options -C, -W, -o and -w require at "
|
||||
errx(EX_USAGE, "ERROR: options -C, -W and -o require at "
|
||||
"least one counting mode PMC to be specified.");
|
||||
|
||||
/* check for sampling mode options without a sampling PMC spec */
|
||||
@ -913,10 +970,10 @@ main(int argc, char **argv)
|
||||
errx(EX_USAGE, "ERROR: options -N, -n and -O require at "
|
||||
"least one sampling mode PMC to be specified.");
|
||||
|
||||
/* check if -g/-G are being used correctly */
|
||||
/* check if -g/-G/-m/-T are being used correctly */
|
||||
if ((args.pa_flags & FLAG_DO_ANALYSIS) &&
|
||||
!(args.pa_flags & (FLAG_HAS_SAMPLING_PMCS|FLAG_READ_LOGFILE)))
|
||||
errx(EX_USAGE, "ERROR: options -g/-G require sampling PMCs "
|
||||
errx(EX_USAGE, "ERROR: options -g/-G/-m/-T require sampling PMCs "
|
||||
"or -R to be specified.");
|
||||
|
||||
/* check if -O was spuriously specified */
|
||||
@ -926,11 +983,11 @@ main(int argc, char **argv)
|
||||
"ERROR: option -O is used only with options "
|
||||
"-E, -P, -S and -W.");
|
||||
|
||||
/* -k kernel path require -g/-G or -R */
|
||||
/* -k kernel path require -g/-G/-m/-T or -R */
|
||||
if ((args.pa_flags & FLAG_HAS_KERNELPATH) &&
|
||||
(args.pa_flags & FLAG_DO_ANALYSIS) == 0 &&
|
||||
(args.pa_flags & FLAG_READ_LOGFILE) == 0)
|
||||
errx(EX_USAGE, "ERROR: option -k is only used with -g/-R.");
|
||||
errx(EX_USAGE, "ERROR: option -k is only used with -g/-R/-m/-T.");
|
||||
|
||||
/* -D only applies to gprof output mode (-g) */
|
||||
if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) &&
|
||||
@ -943,6 +1000,11 @@ main(int argc, char **argv)
|
||||
(args.pa_flags & FLAG_READ_LOGFILE) == 0)
|
||||
errx(EX_USAGE, "ERROR: option -M is only used with -g/-R.");
|
||||
|
||||
/* -T is incompatible with -R (replay logfile is a TODO) */
|
||||
if ((args.pa_flags & FLAG_DO_TOP) &&
|
||||
(args.pa_flags & FLAG_READ_LOGFILE))
|
||||
errx(EX_USAGE, "ERROR: option -T is incompatible with -R.");
|
||||
|
||||
/*
|
||||
* Disallow textual output of sampling PMCs if counting PMCs
|
||||
* have also been asked for, mostly because the combined output
|
||||
@ -996,7 +1058,7 @@ main(int argc, char **argv)
|
||||
"for writing", graphfilename);
|
||||
}
|
||||
}
|
||||
if (args.pa_flags & FLAG_WANTS_MAPPINGS) {
|
||||
if (args.pa_flags & FLAG_DO_ANNOTATE) {
|
||||
args.pa_graphfile = fopen(graphfilename, "w");
|
||||
if (args.pa_graphfile == NULL)
|
||||
err(EX_OSERR, "ERROR: cannot open \"%s\" for writing",
|
||||
@ -1012,13 +1074,13 @@ main(int argc, char **argv)
|
||||
if ((args.pa_flags & FLAG_DO_ANALYSIS) == 0)
|
||||
args.pa_flags |= FLAG_DO_PRINT;
|
||||
|
||||
pmcstat_initialize_logging(&args);
|
||||
pmcstat_initialize_logging();
|
||||
args.pa_logfd = pmcstat_open_log(args.pa_inputpath,
|
||||
PMCSTAT_OPEN_FOR_READ);
|
||||
if ((args.pa_logparser = pmclog_open(args.pa_logfd)) == NULL)
|
||||
err(EX_OSERR, "ERROR: Cannot create parser");
|
||||
pmcstat_process_log(&args);
|
||||
pmcstat_shutdown_logging(&args);
|
||||
pmcstat_process_log();
|
||||
pmcstat_shutdown_logging();
|
||||
exit(EX_OK);
|
||||
}
|
||||
|
||||
@ -1062,7 +1124,9 @@ main(int argc, char **argv)
|
||||
|
||||
args.pa_logfd = pipefd[WRITEPIPEFD];
|
||||
|
||||
args.pa_flags |= (FLAG_HAS_PIPE | FLAG_DO_PRINT);
|
||||
args.pa_flags |= FLAG_HAS_PIPE;
|
||||
if ((args.pa_flags & FLAG_DO_TOP) == 0)
|
||||
args.pa_flags |= FLAG_DO_PRINT;
|
||||
args.pa_logparser = pmclog_open(pipefd[READPIPEFD]);
|
||||
}
|
||||
|
||||
@ -1126,12 +1190,24 @@ main(int argc, char **argv)
|
||||
err(EX_OSERR, "ERROR: Cannot determine window size");
|
||||
|
||||
pmcstat_displayheight = ws.ws_row - 1;
|
||||
pmcstat_displaywidth = ws.ws_col - 1;
|
||||
|
||||
EV_SET(&kev, SIGWINCH, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
|
||||
|
||||
if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot register kevent for "
|
||||
"SIGWINCH");
|
||||
|
||||
args.pa_toptty = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Listen to key input in top mode.
|
||||
*/
|
||||
if (args.pa_flags & FLAG_DO_TOP) {
|
||||
EV_SET(&kev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, NULL);
|
||||
if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot register kevent");
|
||||
}
|
||||
|
||||
EV_SET(&kev, SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
|
||||
@ -1152,9 +1228,13 @@ main(int argc, char **argv)
|
||||
if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot register kevent for SIGCHLD");
|
||||
|
||||
/* setup a timer if we have counting mode PMCs needing to be printed */
|
||||
if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) &&
|
||||
(args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) {
|
||||
/*
|
||||
* Setup a timer if we have counting mode PMCs needing to be printed or
|
||||
* top mode plugin is active.
|
||||
*/
|
||||
if (((args.pa_flags & FLAG_HAS_COUNTING_PMCS) &&
|
||||
(args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) ||
|
||||
(args.pa_flags & FLAG_DO_TOP)) {
|
||||
EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0,
|
||||
args.pa_interval * 1000, NULL);
|
||||
|
||||
@ -1165,7 +1245,7 @@ main(int argc, char **argv)
|
||||
|
||||
/* attach PMCs to the target process, starting it if specified */
|
||||
if (args.pa_flags & FLAG_HAS_COMMANDLINE)
|
||||
pmcstat_create_process(&args);
|
||||
pmcstat_create_process();
|
||||
|
||||
if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot retrieve driver statistics");
|
||||
@ -1176,7 +1256,7 @@ main(int argc, char **argv)
|
||||
errx(EX_DATAERR, "ERROR: No matching target "
|
||||
"processes.");
|
||||
if (args.pa_flags & FLAG_HAS_PROCESS_PMCS)
|
||||
pmcstat_attach_pmcs(&args);
|
||||
pmcstat_attach_pmcs();
|
||||
|
||||
if (pmcstat_kvm) {
|
||||
kvm_close(pmcstat_kvm);
|
||||
@ -1185,16 +1265,16 @@ main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/* start the pmcs */
|
||||
pmcstat_start_pmcs(&args);
|
||||
pmcstat_start_pmcs();
|
||||
|
||||
/* start the (commandline) process if needed */
|
||||
if (args.pa_flags & FLAG_HAS_COMMANDLINE)
|
||||
pmcstat_start_process();
|
||||
|
||||
/* initialize logging if printing the configured log */
|
||||
if ((args.pa_flags & FLAG_DO_PRINT) &&
|
||||
if ((args.pa_flags & (FLAG_DO_PRINT | FLAG_DO_TOP)) &&
|
||||
(args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)))
|
||||
pmcstat_initialize_logging(&args);
|
||||
pmcstat_initialize_logging();
|
||||
|
||||
/* Handle SIGINT using the kqueue loop */
|
||||
sa.sa_handler = SIG_IGN;
|
||||
@ -1204,6 +1284,37 @@ main(int argc, char **argv)
|
||||
if (sigaction(SIGINT, &sa, NULL) < 0)
|
||||
err(EX_OSERR, "ERROR: Cannot install signal handler");
|
||||
|
||||
/*
|
||||
* Setup the top mode display.
|
||||
*/
|
||||
if (args.pa_flags & FLAG_DO_TOP) {
|
||||
args.pa_flags &= ~FLAG_DO_PRINT;
|
||||
|
||||
if (args.pa_toptty) {
|
||||
/*
|
||||
* Init ncurses.
|
||||
*/
|
||||
initscr();
|
||||
if(has_colors() == TRUE) {
|
||||
args.pa_topcolor = 1;
|
||||
start_color();
|
||||
use_default_colors();
|
||||
pair_content(0, &cf, &cb);
|
||||
init_pair(1, COLOR_RED, cb);
|
||||
init_pair(2, COLOR_YELLOW, cb);
|
||||
init_pair(3, COLOR_GREEN, cb);
|
||||
}
|
||||
cbreak();
|
||||
noecho();
|
||||
nonl();
|
||||
nodelay(stdscr, 1);
|
||||
intrflush(stdscr, FALSE);
|
||||
keypad(stdscr, TRUE);
|
||||
clear();
|
||||
atexit(pmcstat_topexit);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* loop till either the target process (if any) exits, or we
|
||||
* are killed by a SIGINT.
|
||||
@ -1225,14 +1336,18 @@ main(int argc, char **argv)
|
||||
case EVFILT_PROC: /* target has exited */
|
||||
if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE |
|
||||
FLAG_HAS_PIPE))
|
||||
runstate = pmcstat_close_log(&args);
|
||||
runstate = pmcstat_close_log();
|
||||
else
|
||||
runstate = PMCSTAT_FINISHED;
|
||||
do_print = 1;
|
||||
break;
|
||||
|
||||
case EVFILT_READ: /* log file data is present */
|
||||
runstate = pmcstat_process_log(&args);
|
||||
if (kev.ident == (unsigned)fileno(stdin)) {
|
||||
if (pmcstat_keypress_log())
|
||||
runstate = pmcstat_close_log();
|
||||
} else
|
||||
runstate = pmcstat_process_log();
|
||||
break;
|
||||
|
||||
case EVFILT_SIGNAL:
|
||||
@ -1253,17 +1368,17 @@ main(int argc, char **argv)
|
||||
*/
|
||||
if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE |
|
||||
FLAG_HAS_PIPE)) {
|
||||
runstate = pmcstat_close_log(&args);
|
||||
runstate = pmcstat_close_log();
|
||||
if (args.pa_flags &
|
||||
(FLAG_DO_PRINT|FLAG_DO_ANALYSIS))
|
||||
pmcstat_process_log(&args);
|
||||
pmcstat_process_log();
|
||||
}
|
||||
do_print = 1; /* print PMCs at exit */
|
||||
runstate = PMCSTAT_FINISHED;
|
||||
} else if (kev.ident == SIGINT) {
|
||||
/* Kill the child process if we started it */
|
||||
if (args.pa_flags & FLAG_HAS_COMMANDLINE)
|
||||
pmcstat_kill_process(&args);
|
||||
pmcstat_kill_process();
|
||||
/* Close the pipe to self, if present. */
|
||||
if (args.pa_flags & FLAG_HAS_PIPE)
|
||||
(void) close(pipefd[READPIPEFD]);
|
||||
@ -1274,6 +1389,7 @@ main(int argc, char **argv)
|
||||
err(EX_OSERR, "ERROR: Cannot determine "
|
||||
"window size");
|
||||
pmcstat_displayheight = ws.ws_row - 1;
|
||||
pmcstat_displaywidth = ws.ws_col - 1;
|
||||
} else
|
||||
assert(0);
|
||||
|
||||
@ -1285,22 +1401,30 @@ main(int argc, char **argv)
|
||||
|
||||
}
|
||||
|
||||
if (do_print &&
|
||||
(args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) {
|
||||
pmcstat_print_pmcs(&args);
|
||||
if (runstate == PMCSTAT_FINISHED && /* final newline */
|
||||
(args.pa_flags & FLAG_DO_PRINT) == 0)
|
||||
(void) fprintf(args.pa_printfile, "\n");
|
||||
if (do_print) {
|
||||
if ((args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) {
|
||||
pmcstat_print_pmcs();
|
||||
if (runstate == PMCSTAT_FINISHED && /* final newline */
|
||||
(args.pa_flags & FLAG_DO_PRINT) == 0)
|
||||
(void) fprintf(args.pa_printfile, "\n");
|
||||
}
|
||||
if (args.pa_flags & FLAG_DO_TOP)
|
||||
pmcstat_display_log();
|
||||
do_print = 0;
|
||||
}
|
||||
|
||||
} while (runstate != PMCSTAT_FINISHED);
|
||||
|
||||
if ((args.pa_flags & FLAG_DO_TOP) && args.pa_toptty) {
|
||||
pmcstat_topexit();
|
||||
args.pa_toptty = 0;
|
||||
}
|
||||
|
||||
/* flush any pending log entries */
|
||||
if (args.pa_flags & (FLAG_HAS_OUTPUT_LOGFILE | FLAG_HAS_PIPE))
|
||||
pmc_flush_logfile();
|
||||
|
||||
pmcstat_cleanup(&args);
|
||||
pmcstat_cleanup();
|
||||
|
||||
free(args.pa_kernel);
|
||||
|
||||
|
@ -47,13 +47,15 @@
|
||||
#define FLAG_HAS_SAMPLESDIR 0x00000800 /* -D dir */
|
||||
#define FLAG_HAS_KERNELPATH 0x00001000 /* -k kernel */
|
||||
#define FLAG_DO_PRINT 0x00002000 /* -o */
|
||||
#define FLAG_DO_CALLGRAPHS 0x00004000 /* -G */
|
||||
#define FLAG_DO_ANALYSIS 0x00008000 /* -g or -G */
|
||||
#define FLAG_WANTS_MAPPINGS 0x00010000 /* -m */
|
||||
#define FLAG_DO_CALLGRAPHS 0x00004000 /* -G or -F */
|
||||
#define FLAG_DO_ANNOTATE 0x00008000 /* -m */
|
||||
#define FLAG_DO_TOP 0x00010000 /* -T */
|
||||
#define FLAG_DO_ANALYSIS 0x00020000 /* -g or -G or -m or -T */
|
||||
|
||||
#define DEFAULT_SAMPLE_COUNT 65536
|
||||
#define DEFAULT_WAIT_INTERVAL 5.0
|
||||
#define DEFAULT_DISPLAY_HEIGHT 23
|
||||
#define DEFAULT_DISPLAY_HEIGHT 256 /* file virtual height */
|
||||
#define DEFAULT_DISPLAY_WIDTH 1024 /* file virtual width */
|
||||
#define DEFAULT_BUFFER_SIZE 4096
|
||||
#define DEFAULT_CALLGRAPH_DEPTH 4
|
||||
|
||||
@ -75,12 +77,24 @@
|
||||
|
||||
#define PMCSTAT_LDD_COMMAND "/usr/bin/ldd"
|
||||
|
||||
#define PMCSTAT_PRINT_ENTRY(A,T,...) do { \
|
||||
(void) fprintf((A)->pa_printfile, "%-9s", T); \
|
||||
(void) fprintf((A)->pa_printfile, " " __VA_ARGS__); \
|
||||
(void) fprintf((A)->pa_printfile, "\n"); \
|
||||
#define PMCSTAT_PRINT_ENTRY(T,...) do { \
|
||||
(void) fprintf(args.pa_printfile, "%-9s", T); \
|
||||
(void) fprintf(args.pa_printfile, " " __VA_ARGS__); \
|
||||
(void) fprintf(args.pa_printfile, "\n"); \
|
||||
} while (0)
|
||||
|
||||
#define PMCSTAT_PL_NONE 0
|
||||
#define PMCSTAT_PL_CALLGRAPH 1
|
||||
#define PMCSTAT_PL_GPROF 2
|
||||
#define PMCSTAT_PL_ANNOTATE 3
|
||||
#define PMCSTAT_PL_CALLTREE 4
|
||||
|
||||
#define PMCSTAT_TOP_DELTA 0
|
||||
#define PMCSTAT_TOP_ACCUM 1
|
||||
|
||||
#define min(A,B) ((A) < (B) ? (A) : (B))
|
||||
#define max(A,B) ((A) > (B) ? (A) : (B))
|
||||
|
||||
enum pmcstat_state {
|
||||
PMCSTAT_FINISHED = 0,
|
||||
PMCSTAT_EXITING = 1,
|
||||
@ -110,6 +124,8 @@ struct pmcstat_target {
|
||||
struct pmcstat_args {
|
||||
int pa_flags; /* argument flags */
|
||||
int pa_required; /* required features */
|
||||
int pa_pplugin; /* pre-processing plugin */
|
||||
int pa_plugin; /* analysis plugin */
|
||||
int pa_verbosity; /* verbosity level */
|
||||
FILE *pa_printfile; /* where to send printed output */
|
||||
int pa_logfd; /* output log file */
|
||||
@ -124,31 +140,44 @@ struct pmcstat_args {
|
||||
int pa_graphdepth; /* print depth for callgraphs */
|
||||
double pa_interval; /* printing interval in seconds */
|
||||
uint32_t pa_cpumask; /* filter for CPUs analysed */
|
||||
int pa_ctdumpinstr; /* dump instructions with calltree */
|
||||
int pa_topmode; /* delta or accumulative */
|
||||
int pa_toptty; /* output to tty or file */
|
||||
int pa_topcolor; /* terminal support color */
|
||||
int pa_mergepmc; /* merge PMC with same name */
|
||||
int pa_argc;
|
||||
char **pa_argv;
|
||||
STAILQ_HEAD(, pmcstat_ev) pa_events;
|
||||
SLIST_HEAD(, pmcstat_target) pa_targets;
|
||||
} args;
|
||||
};
|
||||
|
||||
extern int pmcstat_displayheight; /* current terminal height */
|
||||
extern int pmcstat_displaywidth; /* current terminal width */
|
||||
extern struct pmcstat_args args; /* command line args */
|
||||
|
||||
/* Function prototypes */
|
||||
void pmcstat_attach_pmcs(struct pmcstat_args *_a);
|
||||
void pmcstat_cleanup(struct pmcstat_args *_a);
|
||||
void pmcstat_clone_event_descriptor(struct pmcstat_args *_a,
|
||||
void pmcstat_attach_pmcs(void);
|
||||
void pmcstat_cleanup(void);
|
||||
void pmcstat_clone_event_descriptor(
|
||||
struct pmcstat_ev *_ev, uint32_t _cpumask);
|
||||
int pmcstat_close_log(struct pmcstat_args *_a);
|
||||
void pmcstat_create_process(struct pmcstat_args *_a);
|
||||
void pmcstat_find_targets(struct pmcstat_args *_a, const char *_arg);
|
||||
void pmcstat_initialize_logging(struct pmcstat_args *_a);
|
||||
void pmcstat_kill_process(struct pmcstat_args *_a);
|
||||
int pmcstat_close_log(void);
|
||||
void pmcstat_create_process(void);
|
||||
void pmcstat_find_targets(const char *_arg);
|
||||
void pmcstat_initialize_logging(void);
|
||||
void pmcstat_kill_process(void);
|
||||
int pmcstat_open_log(const char *_p, int _mode);
|
||||
void pmcstat_print_counters(struct pmcstat_args *_a);
|
||||
void pmcstat_print_headers(struct pmcstat_args *_a);
|
||||
void pmcstat_print_pmcs(struct pmcstat_args *_a);
|
||||
void pmcstat_print_counters(void);
|
||||
void pmcstat_print_headers(void);
|
||||
void pmcstat_print_pmcs(void);
|
||||
void pmcstat_show_usage(void);
|
||||
void pmcstat_shutdown_logging(struct pmcstat_args *_a);
|
||||
void pmcstat_start_pmcs(struct pmcstat_args *_a);
|
||||
void pmcstat_shutdown_logging(void);
|
||||
void pmcstat_start_pmcs(void);
|
||||
void pmcstat_start_process(void);
|
||||
int pmcstat_process_log(struct pmcstat_args *_a);
|
||||
int pmcstat_process_log(void);
|
||||
int pmcstat_keypress_log(void);
|
||||
void pmcstat_display_log(void);
|
||||
void pmcstat_pluginconfigure_log(char *_opt);
|
||||
uint32_t pmcstat_get_cpumask(const char *_a);
|
||||
void pmcstat_topexit(void);
|
||||
|
||||
#endif /* _PMCSTAT_H_ */
|
||||
|
File diff suppressed because it is too large
Load Diff
196
usr.sbin/pmcstat/pmcstat_log.h
Normal file
196
usr.sbin/pmcstat/pmcstat_log.h
Normal file
@ -0,0 +1,196 @@
|
||||
/*-
|
||||
* Copyright (c) 2005-2007, Joseph Koshy
|
||||
* Copyright (c) 2007 The FreeBSD Foundation
|
||||
* Copyright (c) 2009, Fabien Thomas
|
||||
* All rights reserved.
|
||||
*
|
||||
* Portions of this software were developed by A. Joseph Koshy under
|
||||
* sponsorship from the FreeBSD Foundation and Google, Inc.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_LOG_H_
|
||||
#define _PMCSTAT_LOG_H_
|
||||
|
||||
typedef const void *pmcstat_interned_string;
|
||||
|
||||
/*
|
||||
* A 'pmcstat_process' structure models processes. Each process is
|
||||
* associated with a set of pmcstat_pcmap structures that map
|
||||
* addresses inside it to executable objects. This set is implemented
|
||||
* as a list, kept sorted in ascending order of mapped addresses.
|
||||
*
|
||||
* 'pp_pid' holds the pid of the process. When a process exits, the
|
||||
* 'pp_isactive' field is set to zero, but the process structure is
|
||||
* not immediately reclaimed because there may still be samples in the
|
||||
* log for this process.
|
||||
*/
|
||||
|
||||
struct pmcstat_process {
|
||||
LIST_ENTRY(pmcstat_process) pp_next; /* hash-next */
|
||||
pid_t pp_pid; /* associated pid */
|
||||
int pp_isactive; /* whether active */
|
||||
uintfptr_t pp_entryaddr; /* entry address */
|
||||
TAILQ_HEAD(,pmcstat_pcmap) pp_map; /* address range map */
|
||||
};
|
||||
extern LIST_HEAD(pmcstat_process_hash_list, pmcstat_process) pmcstat_process_hash[PMCSTAT_NHASH];
|
||||
|
||||
/*
|
||||
* A 'pmcstat_image' structure describes an executable program on
|
||||
* disk. 'pi_execpath' is a cookie representing the pathname of
|
||||
* the executable. 'pi_start' and 'pi_end' are the least and greatest
|
||||
* virtual addresses for the text segments in the executable.
|
||||
* 'pi_gmonlist' contains a linked list of gmon.out files associated
|
||||
* with this image.
|
||||
*/
|
||||
|
||||
enum pmcstat_image_type {
|
||||
PMCSTAT_IMAGE_UNKNOWN = 0, /* never looked at the image */
|
||||
PMCSTAT_IMAGE_INDETERMINABLE, /* can't tell what the image is */
|
||||
PMCSTAT_IMAGE_ELF32, /* ELF 32 bit object */
|
||||
PMCSTAT_IMAGE_ELF64, /* ELF 64 bit object */
|
||||
PMCSTAT_IMAGE_AOUT /* AOUT object */
|
||||
};
|
||||
|
||||
struct pmcstat_image {
|
||||
LIST_ENTRY(pmcstat_image) pi_next; /* hash link */
|
||||
TAILQ_ENTRY(pmcstat_image) pi_lru; /* LRU list */
|
||||
pmcstat_interned_string pi_execpath; /* cookie */
|
||||
pmcstat_interned_string pi_samplename; /* sample path name */
|
||||
pmcstat_interned_string pi_fullpath; /* path to FS object */
|
||||
pmcstat_interned_string pi_name; /* display name */
|
||||
|
||||
enum pmcstat_image_type pi_type; /* executable type */
|
||||
|
||||
/*
|
||||
* Executables have pi_start and pi_end; these are zero
|
||||
* for shared libraries.
|
||||
*/
|
||||
uintfptr_t pi_start; /* start address (inclusive) */
|
||||
uintfptr_t pi_end; /* end address (exclusive) */
|
||||
uintfptr_t pi_entry; /* entry address */
|
||||
uintfptr_t pi_vaddr; /* virtual address where loaded */
|
||||
int pi_isdynamic; /* whether a dynamic object */
|
||||
int pi_iskernelmodule;
|
||||
pmcstat_interned_string pi_dynlinkerpath; /* path in .interp */
|
||||
|
||||
/* All symbols associated with this object. */
|
||||
struct pmcstat_symbol *pi_symbols;
|
||||
size_t pi_symcount;
|
||||
|
||||
/* Handle to addr2line for this image. */
|
||||
FILE *pi_addr2line;
|
||||
|
||||
/*
|
||||
* Plugins private data
|
||||
*/
|
||||
|
||||
/* gprof:
|
||||
* An image can be associated with one or more gmon.out files;
|
||||
* one per PMC.
|
||||
*/
|
||||
LIST_HEAD(,pmcstat_gmonfile) pi_gmlist;
|
||||
};
|
||||
extern LIST_HEAD(pmcstat_image_hash_list, pmcstat_image) pmcstat_image_hash[PMCSTAT_NHASH];
|
||||
|
||||
/*
|
||||
* A 'pmcstat_pcmap' structure maps a virtual address range to an
|
||||
* underlying 'pmcstat_image' descriptor.
|
||||
*/
|
||||
struct pmcstat_pcmap {
|
||||
TAILQ_ENTRY(pmcstat_pcmap) ppm_next;
|
||||
uintfptr_t ppm_lowpc;
|
||||
uintfptr_t ppm_highpc;
|
||||
struct pmcstat_image *ppm_image;
|
||||
};
|
||||
|
||||
/*
|
||||
* Each function symbol tracked by pmcstat(8).
|
||||
*/
|
||||
|
||||
struct pmcstat_symbol {
|
||||
pmcstat_interned_string ps_name;
|
||||
uint64_t ps_start;
|
||||
uint64_t ps_end;
|
||||
};
|
||||
|
||||
/*
|
||||
* 'pmcstat_pmcrecord' is a mapping from PMC ids to human-readable
|
||||
* names.
|
||||
*/
|
||||
|
||||
struct pmcstat_pmcrecord {
|
||||
LIST_ENTRY(pmcstat_pmcrecord) pr_next;
|
||||
pmc_id_t pr_pmcid;
|
||||
int pr_pmcin;
|
||||
pmcstat_interned_string pr_pmcname;
|
||||
struct pmcstat_pmcrecord *pr_merge;
|
||||
};
|
||||
extern LIST_HEAD(pmcstat_pmcs, pmcstat_pmcrecord) pmcstat_pmcs; /* PMC list */
|
||||
|
||||
/*
|
||||
* Misc. statistics
|
||||
*/
|
||||
struct pmcstat_stats {
|
||||
int ps_exec_aout; /* # a.out executables seen */
|
||||
int ps_exec_elf; /* # elf executables seen */
|
||||
int ps_exec_errors; /* # errors processing executables */
|
||||
int ps_exec_indeterminable; /* # unknown executables seen */
|
||||
int ps_samples_total; /* total number of samples processed */
|
||||
int ps_samples_skipped; /* #samples filtered out for any reason */
|
||||
int ps_samples_unknown_offset; /* #samples of rank 0 not in a map */
|
||||
int ps_samples_indeterminable; /* #samples in indeterminable images */
|
||||
int ps_callchain_dubious_frames;/* #dubious frame pointers seen */
|
||||
};
|
||||
extern struct pmcstat_stats pmcstat_stats; /* statistics */
|
||||
|
||||
extern struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */
|
||||
|
||||
extern int pmcstat_npmcs; /* PMC count. */
|
||||
|
||||
/*
|
||||
* Top mode global options.
|
||||
*/
|
||||
float pmcstat_threshold; /* Threshold to filter node. */
|
||||
int pmcstat_pmcinfilter; /* PMC index displayed. */
|
||||
|
||||
/* Function prototypes */
|
||||
const char *pmcstat_pmcid_to_name(pmc_id_t _pmcid);
|
||||
const char *pmcstat_pmcindex_to_name(int pmcin);
|
||||
struct pmcstat_pmcrecord *pmcstat_pmcindex_to_pmcr(int pmcin);
|
||||
struct pmcstat_pcmap *pmcstat_process_find_map(struct pmcstat_process *_p,
|
||||
uintfptr_t _pc);
|
||||
struct pmcstat_symbol *pmcstat_symbol_search(struct pmcstat_image *image,
|
||||
uintfptr_t addr);
|
||||
const char *pmcstat_string_unintern(pmcstat_interned_string _is);
|
||||
pmcstat_interned_string pmcstat_string_intern(const char *_s);
|
||||
void pmcstat_image_determine_type(struct pmcstat_image *_image);
|
||||
pmcstat_interned_string pmcstat_string_lookup(const char *_s);
|
||||
int pmcstat_image_addr2line(struct pmcstat_image *image, uintfptr_t addr,
|
||||
char *sourcefile, size_t sourcefile_len, unsigned *sourceline,
|
||||
char *funcname, size_t funcname_len);
|
||||
|
||||
#endif /* _PMCSTAT_LOG_H_ */
|
||||
|
75
usr.sbin/pmcstat/pmcstat_top.h
Normal file
75
usr.sbin/pmcstat/pmcstat_top.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*-
|
||||
* Copyright (c) 2009, Fabien Thomas
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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$
|
||||
*/
|
||||
|
||||
#ifndef _PMCSTAT_TOP_H_
|
||||
#define _PMCSTAT_TOP_H_
|
||||
|
||||
/* Return the ncurses attributes for the given value. */
|
||||
#define PMCSTAT_ATTRPERCENT(b) \
|
||||
((b) > 10.0 ? (args.pa_topcolor ? COLOR_PAIR(1) : A_BOLD) : \
|
||||
((b) > 5.0 ? (args.pa_topcolor ? COLOR_PAIR(2) : 0) : \
|
||||
((b) > 2.5 ? (args.pa_topcolor ? COLOR_PAIR(3) : 0) : 0)))
|
||||
|
||||
/* Print to the default ncurse windows if on a terminal or to the file. */
|
||||
#define PMCSTAT_PRINTW(...) do { \
|
||||
if (args.pa_toptty) \
|
||||
printw(__VA_ARGS__); \
|
||||
else \
|
||||
fprintf(args.pa_printfile, __VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
/* If ncurses mode active set attributes. */
|
||||
#define PMCSTAT_ATTRON(b) do { \
|
||||
if (args.pa_toptty) \
|
||||
attron(b); \
|
||||
} while (0)
|
||||
|
||||
/* If ncurses mode active unset attributes. */
|
||||
#define PMCSTAT_ATTROFF(b) do { \
|
||||
if (args.pa_toptty) \
|
||||
attroff(b); \
|
||||
} while (0)
|
||||
|
||||
/* Erase screen and set cursor to top left. */
|
||||
#define PMCSTAT_PRINTBEGIN() do { \
|
||||
if (args.pa_toptty) \
|
||||
clear(); \
|
||||
} while (0)
|
||||
|
||||
/* Flush buffer to backend. */
|
||||
#define PMCSTAT_PRINTEND() do { \
|
||||
if (!args.pa_toptty) { \
|
||||
PMCSTAT_PRINTW("---\n"); \
|
||||
fflush(args.pa_printfile); \
|
||||
} else \
|
||||
refresh(); \
|
||||
} while (0)
|
||||
|
||||
/* Function prototypes */
|
||||
|
||||
#endif /* _PMCSTAT_TOP_H_ */
|
Loading…
x
Reference in New Issue
Block a user