Index: head/lib/libpmcstat/libpmcstat.h =================================================================== --- head/lib/libpmcstat/libpmcstat.h (revision 363927) +++ head/lib/libpmcstat/libpmcstat.h (revision 363928) @@ -1,387 +1,388 @@ /*- * 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 _LIBPMCSTAT_H_ #define _LIBPMCSTAT_H_ #include #include #include #include #define PMCSTAT_ALLOCATE 1 #define NSOCKPAIRFD 2 #define PARENTSOCKET 0 #define CHILDSOCKET 1 #define PMCSTAT_OPEN_FOR_READ 0 #define PMCSTAT_OPEN_FOR_WRITE 1 #define READPIPEFD 0 #define WRITEPIPEFD 1 #define NPIPEFD 2 #define PMCSTAT_NHASH 256 #define PMCSTAT_HASH_MASK 0xFF #define DEFAULT_SAMPLE_COUNT 65536 typedef const void *pmcstat_interned_string; struct pmc_plugins; enum pmcstat_state { PMCSTAT_FINISHED = 0, PMCSTAT_EXITING = 1, PMCSTAT_RUNNING = 2 }; struct pmcstat_ev { STAILQ_ENTRY(pmcstat_ev) ev_next; int ev_count; /* associated count if in sampling mode */ uint32_t ev_cpu; /* cpus for this event */ int ev_cumulative; /* show cumulative counts */ int ev_flags; /* PMC_F_* */ int ev_fieldskip; /* #leading spaces */ int ev_fieldwidth; /* print width */ enum pmc_mode ev_mode; /* desired mode */ char *ev_name; /* (derived) event name */ pmc_id_t ev_pmcid; /* allocated ID */ pmc_value_t ev_saved; /* for incremental counts */ char *ev_spec; /* event specification */ }; struct pmcstat_target { SLIST_ENTRY(pmcstat_target) pt_next; pid_t pt_pid; }; struct pmcstat_args { int pa_flags; /* argument flags */ #define FLAG_HAS_TARGET 0x00000001 /* process target */ #define FLAG_HAS_WAIT_INTERVAL 0x00000002 /* -w secs */ #define FLAG_HAS_OUTPUT_LOGFILE 0x00000004 /* -O file or pipe */ #define FLAG_HAS_COMMANDLINE 0x00000008 /* command */ #define FLAG_HAS_SAMPLING_PMCS 0x00000010 /* -S or -P */ #define FLAG_HAS_COUNTING_PMCS 0x00000020 /* -s or -p */ #define FLAG_HAS_PROCESS_PMCS 0x00000040 /* -P or -p */ #define FLAG_HAS_SYSTEM_PMCS 0x00000080 /* -S or -s */ #define FLAG_HAS_PIPE 0x00000100 /* implicit log */ #define FLAG_READ_LOGFILE 0x00000200 /* -R file */ #define FLAG_DO_GPROF 0x00000400 /* -g */ #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 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 FLAGS_HAS_CPUMASK 0x00040000 /* -c */ #define FLAG_HAS_DURATION 0x00080000 /* -l secs */ #define FLAG_DO_WIDE_GPROF_HC 0x00100000 /* -e */ -#define FLAG_SKIP_TOP_FN_RES 0x00200000 /* -I */ +#define FLAG_SKIP_TOP_FN_RES 0x00200000 /* -A */ #define FLAG_FILTER_THREAD_ID 0x00400000 /* -L */ +#define FLAG_SHOW_OFFSET 0x00800000 /* -I */ 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 */ char *pa_inputpath; /* path to input log */ char *pa_outputpath; /* path to output log */ void *pa_logparser; /* log file parser */ const char *pa_fsroot; /* FS root where executables reside */ char *pa_kernel; /* pathname of the kernel */ const char *pa_samplesdir; /* directory for profile files */ const char *pa_mapfilename;/* mapfile name */ FILE *pa_graphfile; /* where to send the callgraph */ int pa_graphdepth; /* print depth for callgraphs */ double pa_interval; /* printing interval in seconds */ cpuset_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 */ double pa_duration; /* time duration */ uint32_t pa_tid; int pa_argc; char **pa_argv; STAILQ_HEAD(, pmcstat_ev) pa_events; SLIST_HEAD(, pmcstat_target) pa_targets; }; /* * Each function symbol tracked by pmcstat(8). */ struct pmcstat_symbol { pmcstat_interned_string ps_name; uint64_t ps_start; uint64_t ps_end; }; /* * 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 */ 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 simple implementation of interned strings. Each interned string * is assigned a unique address, so that subsequent string compares * can be done by a simple pointer comparison instead of using * strcmp(). This speeds up hash table lookups and saves memory if * duplicate strings are the norm. */ struct pmcstat_string { LIST_ENTRY(pmcstat_string) ps_next; /* hash link */ int ps_len; int ps_hash; char *ps_string; }; /* * 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; }; /* * 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]; /* * '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; int pr_samples; int pr_dubious_frames; struct pmcstat_pmcrecord *pr_merge; }; extern LIST_HEAD(pmcstat_pmcs, pmcstat_pmcrecord) pmcstat_pmcs; /* PMC list */ struct pmc_plugins { const char *pl_name; /* configure */ int (*pl_configure)(char *opt); /* init and shutdown */ int (*pl_init)(void); void (*pl_shutdown)(FILE *mf); /* sample processing */ void (*pl_process)(struct pmcstat_process *pp, struct pmcstat_pmcrecord *pmcr, uint32_t nsamples, uintfptr_t *cc, int usermode, uint32_t cpu); /* image */ void (*pl_initimage)(struct pmcstat_image *pi); void (*pl_shutdownimage)(struct pmcstat_image *pi); /* pmc */ void (*pl_newpmc)(pmcstat_interned_string ps, struct pmcstat_pmcrecord *pr); /* top display */ void (*pl_topdisplay)(void); /* top keypress */ int (*pl_topkeypress)(int c, void *w); }; /* * 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_samples_unknown_function;/* #samples with unknown function at offset */ int ps_callchain_dubious_frames;/* #dubious frame pointers seen */ }; __BEGIN_DECLS int pmcstat_symbol_compare(const void *a, const void *b); struct pmcstat_symbol *pmcstat_symbol_search(struct pmcstat_image *image, uintfptr_t addr); void pmcstat_image_add_symbols(struct pmcstat_image *image, Elf *e, Elf_Scn *scn, GElf_Shdr *sh); const char *pmcstat_string_unintern(pmcstat_interned_string _is); pmcstat_interned_string pmcstat_string_intern(const char *_s); int pmcstat_string_compute_hash(const char *s); pmcstat_interned_string pmcstat_string_lookup(const char *_s); void pmcstat_image_get_elf_params(struct pmcstat_image *image, struct pmcstat_args *args); struct pmcstat_image * pmcstat_image_from_path(pmcstat_interned_string internedpath, int iskernelmodule, struct pmcstat_args *args, struct pmc_plugins *plugins); int pmcstat_string_lookup_hash(pmcstat_interned_string _is); void pmcstat_process_elf_exec(struct pmcstat_process *_pp, struct pmcstat_image *_image, uintfptr_t _entryaddr, struct pmcstat_args *args, struct pmc_plugins *plugins, struct pmcstat_stats *pmcstat_stats); void pmcstat_image_link(struct pmcstat_process *_pp, struct pmcstat_image *_i, uintfptr_t _lpc); void pmcstat_process_aout_exec(struct pmcstat_process *_pp, struct pmcstat_image *_image, uintfptr_t _entryaddr); void pmcstat_process_exec(struct pmcstat_process *_pp, pmcstat_interned_string _path, uintfptr_t _entryaddr, struct pmcstat_args *args, struct pmc_plugins *plugins, struct pmcstat_stats *pmcstat_stats); void pmcstat_image_determine_type(struct pmcstat_image *_image, struct pmcstat_args *args); void pmcstat_image_get_aout_params(struct pmcstat_image *_image, struct pmcstat_args *args); struct pmcstat_pcmap *pmcstat_process_find_map(struct pmcstat_process *_p, uintfptr_t _pc); void pmcstat_initialize_logging(struct pmcstat_process **pmcstat_kernproc, struct pmcstat_args *args, struct pmc_plugins *plugins, int *pmcstat_npmcs, int *pmcstat_mergepmc); void pmcstat_shutdown_logging(struct pmcstat_args *args, struct pmc_plugins *plugins, struct pmcstat_stats *pmcstat_stats); struct pmcstat_process *pmcstat_process_lookup(pid_t _pid, int _allocate); void pmcstat_clone_event_descriptor(struct pmcstat_ev *ev, const cpuset_t *cpumask, struct pmcstat_args *args); void pmcstat_create_process(int *pmcstat_sockpair, struct pmcstat_args *args, int pmcstat_kq); void pmcstat_start_process(int *pmcstat_sockpair); void pmcstat_attach_pmcs(struct pmcstat_args *args); struct pmcstat_symbol *pmcstat_symbol_search_by_name(struct pmcstat_process *pp, const char *pi_name, const char *name, uintptr_t *, uintptr_t *); void pmcstat_string_initialize(void); void pmcstat_string_shutdown(void); int pmcstat_analyze_log(struct pmcstat_args *args, struct pmc_plugins *plugins, struct pmcstat_stats *pmcstat_stats, struct pmcstat_process *pmcstat_kernproc, int pmcstat_mergepmc, int *pmcstat_npmcs, int *ps_samples_period); int pmcstat_open_log(const char *_p, int _mode); int pmcstat_close_log(struct pmcstat_args *args); __END_DECLS #endif /* !_LIBPMCSTAT_H_ */ Index: head/usr.sbin/pmcstat/pmcpl_callgraph.c =================================================================== --- head/usr.sbin/pmcstat/pmcpl_callgraph.c (revision 363927) +++ head/usr.sbin/pmcstat/pmcpl_callgraph.c (revision 363928) @@ -1,695 +1,710 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pmcstat.h" #include "pmcstat_log.h" #include "pmcstat_top.h" #include "pmcpl_callgraph.h" #define min(A,B) ((A) < (B) ? (A) : (B)) #define max(A,B) ((A) > (B) ? (A) : (B)) /* 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; - else - pmcstat_stats.ps_samples_unknown_function++; + if (!(args.pa_flags & (FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET))) { + if ((sym = pmcstat_symbol_search(image, pc)) != NULL) + pc = sym->ps_start; + else + pmcstat_stats.ps_samples_unknown_function++; + } 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; child = parent = pmcstat_cgnode_hash_lookup_pc(pp, pmcid, pc, usermode); if (parent == NULL) { pmcstat_stats.ps_callchain_dubious_frames++; pmcr->pr_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) continue; 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 __unused, 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; /* Format value. */ v = PMCPL_CG_COUNTP(cg); snprintf(vs, sizeof(vs), "%.1f", v); v_attrs = PMCSTAT_ATTRPERCENT(v); - sym = NULL; /* Format name. */ - if (!(args.pa_flags & FLAG_SKIP_TOP_FN_RES)) - 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 + sym = pmcstat_symbol_search(cg->pcg_image, cg->pcg_func); + if (sym == NULL) { snprintf(ns, sizeof(ns), "%p", (void *)(cg->pcg_image->pi_vaddr + cg->pcg_func)); + } else { + switch (args.pa_flags & (FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET)) { + case FLAG_SKIP_TOP_FN_RES | FLAG_SHOW_OFFSET: + case FLAG_SKIP_TOP_FN_RES: + snprintf(ns, sizeof(ns), "%p", + (void *)(cg->pcg_image->pi_vaddr + cg->pcg_func)); + break; + case FLAG_SHOW_OFFSET: + snprintf(ns, sizeof(ns), "%s+%#0lx", + pmcstat_string_unintern(sym->ps_name), + cg->pcg_func - sym->ps_start); + break; + default: + snprintf(ns, sizeof(ns), "%s", + pmcstat_string_unintern(sym->ps_name)); + break; + } + } PMCSTAT_ATTRON(v_attrs); PMCSTAT_PRINTW("%5.5s", vs); PMCSTAT_ATTROFF(v_attrs); - PMCSTAT_PRINTW(" %-10.10s %-20.20s", + PMCSTAT_PRINTW(" %-10.10s %-30.30s", 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); if (!pmcr) err(EX_SOFTWARE, "ERROR: invalid pmcindex"); /* * 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", + PMCSTAT_PRINTW("%5.5s %-10.10s %-30.30s %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, void *arg) { WINDOW *w; w = (WINDOW *)arg; (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); } } } Index: head/usr.sbin/pmcstat/pmcstat.8 =================================================================== --- head/usr.sbin/pmcstat/pmcstat.8 (revision 363927) +++ head/usr.sbin/pmcstat/pmcstat.8 (revision 363928) @@ -1,524 +1,527 @@ .\" Copyright (c) 2003-2008 Joseph Koshy .\" Copyright (c) 2007 The FreeBSD Foundation .\" 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 Joseph Koshy ``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 Joseph Koshy be liable .\" for any direct, indirect, incidental, special, exemplary, or consequential .\" damages (including, but not limited to, procurement of substitute goods .\" or services; loss of use, data, or profits; or business interruption) .\" however caused and on any theory of liability, whether in contract, strict .\" liability, or tort (including negligence or otherwise) arising in any way .\" out of the use of this software, even if advised of the possibility of .\" such damage. .\" .\" $FreeBSD$ .\" -.Dd May 25, 2018 +.Dd August 5, 2020 .Dt PMCSTAT 8 .Os .Sh NAME .Nm pmcstat .Nd "performance measurement with performance monitoring hardware" .Sh SYNOPSIS .Nm +.Op Fl A .Op Fl C .Op Fl D Ar pathname .Op Fl E .Op Fl F Ar pathname .Op Fl G Ar pathname .Op Fl I .Op Fl L .Op Fl M Ar mapfilename .Op Fl N .Op Fl O Ar logfilename .Op Fl P Ar event-spec .Op Fl R Ar logfilename .Op Fl S Ar event-spec .Op Fl T .Op Fl U .Op Fl W .Op Fl a Ar pathname .Op Fl c Ar cpu-spec .Op Fl d .Op Fl e .Op Fl f Ar pluginopt .Op Fl g .Op Fl i Ar lwp .Op Fl k Ar kerneldir .Op Fl l Ar secs .Op Fl m Ar pathname .Op Fl n Ar rate .Op Fl o Ar outputfile .Op Fl p Ar event-spec .Op Fl q .Op Fl r Ar fsroot .Op Fl s Ar event-spec .Op Fl t Ar process-spec .Op Fl u Ar event-spec .Op Fl v .Op Fl w Ar secs .Op Fl z Ar graphdepth .Op Ar command Op Ar args .Sh DESCRIPTION The .Nm utility measures system performance using the facilities provided by .Xr hwpmc 4 . .Pp The .Nm utility can measure both hardware events seen by the system as a whole, and those seen when a specified set of processes are executing on the system's CPUs. If a specific set of processes is being targeted (for example, if the .Fl t Ar process-spec option is specified, or if a command line is specified using .Ar command ) , then measurement occurs till .Ar command exits, or till all target processes specified by the .Fl t Ar process-spec options exit, or till the .Nm utility is interrupted by the user. If a specific set of processes is not targeted for measurement, then .Nm will perform system-wide measurements till interrupted by the user. .Pp A given invocation of .Nm can mix allocations of system-mode and process-mode PMCs, of both counting and sampling flavors. The values of all counting PMCs are printed in human readable form at regular intervals by .Nm . The output of sampling PMCs may be configured to go to a log file for subsequent offline analysis, or, at the expense of greater overhead, may be configured to be printed in text form on the fly. .Pp Hardware events to measure are specified to .Nm using event specifier strings .Ar event-spec . The syntax of these event specifiers is machine dependent and is documented in .Xr pmc 3 . .Pp A process-mode PMC may be configured to be inheritable by the target process' current and future children. .Sh OPTIONS The following options are available: .Bl -tag -width indent +.It Fl A +Skip symbol lookup and display address instead. .It Fl C Toggle between showing cumulative or incremental counts for subsequent counting mode PMCs specified on the command line. The default is to show incremental counts. .It Fl D Ar pathname Create files with per-program samples in the directory named by .Ar pathname . The default is to create these files in the current directory. .It Fl E Toggle showing per-process counts at the time a tracked process exits for subsequent process-mode PMCs specified on the command line. This option is useful for mapping the performance characteristics of a 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 . If argument .Ar pathname is a .Dq Li - this information is sent to the output file specified by the .Fl o option. .It Fl I -Skip symbol lookup and display address instead. +Show the offset of the instruction pointer into the symbol. .It Fl L List all event names. .It Fl M Ar mapfilename Write the mapping between executable objects encountered in the event log and the abbreviated pathnames used for .Xr gprof 1 profiles to file .Ar mapfilename . If this option is not specified, mapping information is not written. Argument .Ar mapfilename may be a .Dq Li - in which case this mapping information is sent to the output file configured by the .Fl o option. .It Fl N Toggle capturing callchain information for subsequent sampling PMCs. The default is for sampling PMCs to capture callchain information. .It Fl O Ar logfilename Send logging output to file .Ar logfilename . If .Ar logfilename is of the form .Ar hostname Ns : Ns Ar port , where .Ar hostname does not start with a .Ql \&. or a .Ql / , then .Nm will open a network socket to host .Ar hostname on port .Ar port . .Pp If the .Fl O option is not specified and one of the logging options is requested, then .Nm will print a textual form of the logged events to the configured output file. .It Fl P Ar event-spec Allocate a process mode sampling PMC measuring hardware events specified in .Ar event-spec . .It Fl R Ar logfilename Perform offline analysis using sampling data in file .Ar logfilename . .It Fl S Ar event-spec 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. +can be used: 'A' toggle symbol resolution, 'c+a' switch to accumulative mode, 'c+d' +switch to delta mode, 'I' toggle showing offsets into symbols, '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 U Toggle capturing user-space call traces while in kernel mode. The default is for sampling PMCs to capture user-space callchain information while in user-space mode, and kernel callchain information while in kernel mode. .It Fl W Toggle logging the incremental counts seen by the threads of a tracked process each time they are scheduled on a CPU. This is an experimental feature intended to help analyse the dynamic behaviour of processes in the system. It may incur substantial overhead if enabled. The default is for this feature to be disabled. .It Fl a Ar pathname Perform a symbol and file:line lookup for each address in each callgraph and save the output to .Ar pathname . Unlike .Fl m that only resolves the first symbol in the graph, this resolves every node in the callgraph, or prints out addresses if no lookup information is available. This option requires the .Fl R option to read in samples that were previously collected and saved with the .Fl O option. .It Fl c Ar cpu-spec Set the cpus for subsequent system mode PMCs specified on the command line to .Ar cpu-spec . Argument .Ar cpu-spec is a comma separated list of CPU numbers, or the literal .Sq * denoting all available CPUs. The default is to allocate system mode PMCs on all available CPUs. .It Fl d 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 has to be passed in the command line prior to .Fl p , .Fl s , .Fl P , or .Fl S ) . .It Fl e Specify that the gprof profile files will use a wide history counter. These files are produced in a format compatible with .Xr gprof 1 . However, other tools that cannot fully parse a BSD-style gmon header might be unable to correctly parse these files. .It Fl f Ar pluginopt Pass option string to the active plugin. .br threshold= 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 . A separate profile file is generated for each executable object encountered. Profile files are placed in sub-directories named by their PMC event name. .It Fl i Ar lwp Filter on thread ID .Ar lwp , which you can get from .Xr ps 1 .Fl o .Li lwp . .It Fl k Ar kerneldir Set the pathname of the kernel directory to argument .Ar kerneldir . This directory specifies where .Nm should look for the kernel and its modules. The default is to use the path of the running kernel obtained from the .Va kern.bootfile sysctl. Modules will also be searched for in /boot/modules if not found in .Ar kerneldir . .It Fl l Ar secs Set system-wide performance measurement duration for .Ar secs seconds. The argument .Ar secs may be a fractional value. .It Fl m Ar pathname Print the sampled PCs with the name, the start and ending addresses of the function within they live. The .Ar pathname argument is mandatory and indicates where the information will be stored. If argument .Ar pathname is a .Dq Li - this information is sent to the output file specified by the .Fl o option. This option requires the .Fl R option to read in samples that were previously collected and saved with the .Fl O option. .It Fl n Ar rate Set the default sampling rate for subsequent sampling mode PMCs specified on the command line. The default is to configure PMCs to sample the CPU's instruction pointer every 65536 events. .It Fl o Ar outputfile Send counter readings and textual representations of logged data to file .Ar outputfile . The default is to send output to .Pa stderr when collecting live data and to .Pa stdout when processing a pre-existing logfile. .It Fl p Ar event-spec Allocate a process mode counting PMC measuring hardware events specified in .Ar event-spec . .It Fl q Decrease verbosity. .It Fl r Ar fsroot Set the top of the filesystem hierarchy under which executables are located to argument .Ar fsroot . The default is .Pa / . .It Fl s Ar event-spec Allocate a system mode counting PMC measuring hardware events specified in .Ar event-spec . .It Fl t Ar process-spec Attach process mode PMCs to the processes named by argument .Ar process-spec . Argument .Ar process-spec may be a non-negative integer denoting a specific process id, or a regular expression for selecting processes based on their command names. .It Fl u Ar event-spec Provide short description of event. .It Fl v Increase verbosity. .It Fl w Ar secs Print the values of all counting mode PMCs or sampling mode PMCs for top mode every .Ar secs seconds. The argument .Ar secs may be a fractional value. The default interval is 5 seconds. .It Fl z Ar graphdepth When printing system-wide callgraphs, limit callgraphs to the depth specified by argument .Ar graphdepth . .El .Pp If .Ar command is specified, it is executed using .Xr execvp 3 . .Sh EXAMPLES To perform system-wide statistical sampling on an AMD Athlon CPU with samples taken every 32768 instruction retirals and data being sampled to file .Pa sample.stat , use: .Dl "pmcstat -O sample.stat -n 32768 -S k7-retired-instructions" .Pp To execute .Nm firefox and measure the number of data cache misses suffered by it and its children every 12 seconds on an AMD Athlon, use: .Dl "pmcstat -d -w 12 -p k7-dc-misses firefox" .Pp To measure instructions retired for all processes named .Dq emacs use: .Dl "pmcstat -t '^emacs$' -p instructions" .Pp To measure instructions retired for processes named .Dq emacs for a period of 10 seconds use: .Dl "pmcstat -t '^emacs$' -p instructions sleep 10" .Pp To count instruction tlb-misses on CPUs 0 and 2 on a Intel Pentium Pro/Pentium III SMP system use: .Dl "pmcstat -c 0,2 -s p6-itlb-miss" .Pp To collect profiling information for a specific process with pid 1234 based on instruction cache misses seen by it use: .Dl "pmcstat -P ic-misses -t 1234 -O /tmp/sample.out" .Pp To perform system-wide sampling on all configured processors based on processor instructions retired use: .Dl "pmcstat -S instructions -O /tmp/sample.out" If callgraph capture is not desired use: .Dl "pmcstat -N -S instructions -O /tmp/sample.out" .Pp To send the generated event log to a remote machine use: .Dl "pmcstat -S instructions -O remotehost:port" On the remote machine, the sample log can be collected using .Xr nc 1 : .Dl "nc -l remotehost port > /tmp/sample.out" .Pp To generate .Xr gprof 1 compatible profiles from a sample file use: .Dl "pmcstat -R /tmp/sample.out -g" .Pp To print a system-wide profile with callgraphs to file .Pa "foo.graph" use: .Dl "pmcstat -R /tmp/sample.out -G foo.graph" .Sh DIAGNOSTICS If option .Fl v is specified, .Nm may issue the following diagnostic messages: .Bl -diag .It "#callchain/dubious-frames" The number of callchain records that had an .Dq impossible value for a return address. .It "#exec handling errors" The number of .Xr exec 2 events in the log file that named executables that could not be analyzed. .It "#exec/elf" The number of .Xr exec 2 events that named ELF executables. .It "#exec/unknown" The number of .Xr exec 2 events that named executables with unrecognized formats. .It "#samples/total" The total number of samples in the log file. .It "#samples/unclaimed" The number of samples that could not be correlated to a known executable object (i.e., to an executable, shared library, the kernel or the runtime loader). .It "#samples/unknown-object" The number of samples that were associated with an executable with an unrecognized object format. .El .Pp .Ex -std .Sh COMPATIBILITY Due to the limitations of the .Pa gmon.out file format, .Xr gprof 1 compatible profiles generated by the .Fl g option do not contain information about calls that cross executable boundaries. The generated .Pa gmon.out files are also only meaningful for native executables. .Sh SEE ALSO .Xr gprof 1 , .Xr nc 1 , .Xr execvp 3 , .Xr pmc 3 , .Xr pmclog 3 , .Xr hwpmc 4 , .Xr pmccontrol 8 , .Xr sysctl 8 .Sh HISTORY The .Nm utility first appeared in .Fx 6.0 . It is .Ud .Sh AUTHORS .An Joseph Koshy Aq Mt jkoshy@FreeBSD.org .Sh BUGS The .Nm utility cannot yet analyse .Xr hwpmc 4 logs generated by non-native architectures. Index: head/usr.sbin/pmcstat/pmcstat.c =================================================================== --- head/usr.sbin/pmcstat/pmcstat.c (revision 363927) +++ head/usr.sbin/pmcstat/pmcstat.c (revision 363928) @@ -1,1461 +1,1466 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2003-2008, 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pmcstat.h" /* * A given invocation of pmcstat(8) can manage multiple PMCs of both * the system-wide and per-process variety. Each of these could be in * 'counting mode' or in 'sampling mode'. * * For 'counting mode' PMCs, pmcstat(8) will periodically issue a * pmc_read() at the configured time interval and print out the value * of the requested PMCs. * * For 'sampling mode' PMCs it can log to a file for offline analysis, * or can analyse sampling data "on the fly", either by converting * samples to printed textual form or by creating gprof(1) compatible * profiles, one per program executed. When creating gprof(1) * profiles it can optionally merge entries from multiple processes * for a given executable into a single profile file. * * pmcstat(8) can also execute a command line and attach PMCs to the * resulting child process. The protocol used is as follows: * * - parent creates a socketpair for two way communication and * fork()s. * - subsequently: * * /Parent/ /Child/ * * - Wait for childs token. * - Sends token. * - Awaits signal to start. * - Attaches PMCs to the child's pid * and starts them. Sets up * monitoring for the child. * - Signals child to start. * - Receives signal, attempts exec(). * * After this point normal processing can happen. */ /* Globals */ int pmcstat_displayheight = DEFAULT_DISPLAY_HEIGHT; int pmcstat_displaywidth = DEFAULT_DISPLAY_WIDTH; static int pmcstat_sockpair[NSOCKPAIRFD]; static int pmcstat_kq; static kvm_t *pmcstat_kvm; static struct kinfo_proc *pmcstat_plist; struct pmcstat_args args; static void pmcstat_get_cpumask(const char *cpuspec, cpuset_t *cpumask) { int cpu; const char *s; char *end; CPU_ZERO(cpumask); s = cpuspec; do { cpu = strtol(s, &end, 0); if (cpu < 0 || end == s) errx(EX_USAGE, "ERROR: Illegal CPU specification \"%s\".", cpuspec); CPU_SET(cpu, cpumask); s = end + strspn(end, ", \t"); } while (*s); assert(!CPU_EMPTY(cpumask)); } void pmcstat_cleanup(void) { struct pmcstat_ev *ev; /* release allocated PMCs. */ STAILQ_FOREACH(ev, &args.pa_events, ev_next) if (ev->ev_pmcid != PMC_ID_INVALID) { if (pmc_stop(ev->ev_pmcid) < 0) err(EX_OSERR, "ERROR: cannot stop pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); if (pmc_release(ev->ev_pmcid) < 0) err(EX_OSERR, "ERROR: cannot release pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); } /* de-configure the log file if present. */ if (args.pa_flags & (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE)) (void) pmc_configure_logfile(-1); if (args.pa_logparser) { pmclog_close(args.pa_logparser); args.pa_logparser = NULL; } pmcstat_log_shutdown_logging(); } void pmcstat_find_targets(const char *spec) { int n, nproc, pid, rv; struct pmcstat_target *pt; char errbuf[_POSIX2_LINE_MAX], *end; static struct kinfo_proc *kp; regex_t reg; regmatch_t regmatch; /* First check if we've been given a process id. */ pid = strtol(spec, &end, 0); if (end != spec && pid >= 0) { if ((pt = malloc(sizeof(*pt))) == NULL) goto outofmemory; pt->pt_pid = pid; SLIST_INSERT_HEAD(&args.pa_targets, pt, pt_next); return; } /* Otherwise treat arg as a regular expression naming processes. */ if (pmcstat_kvm == NULL) { if ((pmcstat_kvm = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf)) == NULL) err(EX_OSERR, "ERROR: Cannot open kernel \"%s\"", errbuf); if ((pmcstat_plist = kvm_getprocs(pmcstat_kvm, KERN_PROC_PROC, 0, &nproc)) == NULL) err(EX_OSERR, "ERROR: Cannot get process list: %s", kvm_geterr(pmcstat_kvm)); } else nproc = 0; if ((rv = regcomp(®, spec, REG_EXTENDED|REG_NOSUB)) != 0) { regerror(rv, ®, errbuf, sizeof(errbuf)); err(EX_DATAERR, "ERROR: Failed to compile regex \"%s\": %s", spec, errbuf); } for (n = 0, kp = pmcstat_plist; n < nproc; n++, kp++) { if ((rv = regexec(®, kp->ki_comm, 1, ®match, 0)) == 0) { if ((pt = malloc(sizeof(*pt))) == NULL) goto outofmemory; pt->pt_pid = kp->ki_pid; 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", errbuf); } } regfree(®); return; outofmemory: errx(EX_SOFTWARE, "Out of memory."); /*NOTREACHED*/ } void pmcstat_kill_process(void) { struct pmcstat_target *pt; 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(&args.pa_targets); assert(pt != NULL); if (kill(pt->pt_pid, SIGINT) != 0) err(EX_OSERR, "ERROR: cannot signal child process"); } void pmcstat_start_pmcs(void) { struct pmcstat_ev *ev; STAILQ_FOREACH(ev, &args.pa_events, ev_next) { assert(ev->ev_pmcid != PMC_ID_INVALID); if (pmc_start(ev->ev_pmcid) < 0) { warn("ERROR: Cannot start pmc 0x%x \"%s\"", ev->ev_pmcid, ev->ev_name); pmcstat_cleanup(); exit(EX_OSERR); } } } void pmcstat_print_headers(void) { struct pmcstat_ev *ev; int c, w; (void) fprintf(args.pa_printfile, PRINT_HEADER_PREFIX); 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(args.pa_printfile, "%*s", ev->ev_fieldskip, ""); w = ev->ev_fieldwidth - ev->ev_fieldskip - 2; if (c == 's') (void) fprintf(args.pa_printfile, "s/%02d/%-*s ", ev->ev_cpu, w-3, ev->ev_name); else (void) fprintf(args.pa_printfile, "p/%*s ", w, ev->ev_name); } (void) fflush(args.pa_printfile); } void pmcstat_print_counters(void) { int extra_width; struct pmcstat_ev *ev; pmc_value_t value; extra_width = sizeof(PRINT_HEADER_PREFIX) - 1; STAILQ_FOREACH(ev, &args.pa_events, ev_next) { /* skip sampling mode counters */ if (PMC_IS_SAMPLING_MODE(ev->ev_mode)) continue; if (pmc_read(ev->ev_pmcid, &value) < 0) err(EX_OSERR, "ERROR: Cannot read pmc \"%s\"", ev->ev_name); (void) fprintf(args.pa_printfile, "%*ju ", ev->ev_fieldwidth + extra_width, (uintmax_t) ev->ev_cumulative ? value : (value - ev->ev_saved)); if (ev->ev_cumulative == 0) ev->ev_saved = value; extra_width = 0; } (void) fflush(args.pa_printfile); } /* * Print output */ void pmcstat_print_pmcs(void) { static int linecount = 0; /* check if we need to print a header line */ if (++linecount > pmcstat_displayheight) { (void) fprintf(args.pa_printfile, "\n"); linecount = 1; } if (linecount == 1) pmcstat_print_headers(); (void) fprintf(args.pa_printfile, "\n"); pmcstat_print_counters(); return; } void pmcstat_show_usage(void) { errx(EX_USAGE, "[options] [commandline]\n" "\t Measure process and/or system performance using hardware\n" "\t performance monitoring counters.\n" "\t Options include:\n" "\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 -I\t\t don't resolve leaf function name, show address instead\n" "\t -L\t\t list all counters available on this host\n" "\t -M file\t print executable/gmon file map to \"file\"\n" "\t -N\t\t (toggle) capture callchains\n" "\t -O file\t send log output to \"file\"\n" "\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 -U \t\n merged user kernel stack capture\n" "\t -W\t\t (toggle) show counts per context switch\n" "\t -a file\t print sampled PCs and callgraph to \"file\"\n" "\t -c cpu-list\t set cpus for subsequent system-wide PMCs\n" "\t -d\t\t (toggle) track descendants\n" "\t -e\t\t use wide history counter for gprof(1) output\n" "\t -f spec\t pass \"spec\" to as plugin option\n" "\t -g\t\t produce gprof(1) compatible profiles\n" "\t -i lwp\t\t filter on thread id \"lwp\" in post-processing\n" "\t -k dir\t\t set the path to the kernel\n" "\t -l secs\t set duration time\n" "\t -m file\t print sampled PCs to \"file\"\n" "\t -n rate\t set sampling rate\n" "\t -o file\t send print output to \"file\"\n" "\t -p spec\t allocate a process-private counting PMC\n" "\t -q\t\t suppress verbosity\n" "\t -r fsroot\t specify FS root directory\n" "\t -s spec\t allocate a system-wide counting PMC\n" "\t -t process-spec attach to running processes matching " "\"process-spec\"\n" "\t -u spec \t provide short description of counters matching spec\n" "\t -v\t\t increase verbosity\n" "\t -w secs\t set printing time interval\n" "\t -z depth\t limit callchain display depth" ); } /* * At exit handler for top mode */ void pmcstat_topexit(void) { if (!args.pa_toptty) return; /* * Shutdown ncurses. */ clrtoeol(); refresh(); endwin(); } /* * Main */ int main(int argc, char **argv) { cpuset_t cpumask, rootmask; double interval; double duration; int option, npmc; int c, check_driver_stats; int do_callchain, do_descendants, do_logproccsw, do_logprocexit; int do_print, do_read, do_listcounters, do_descr; int do_userspace; size_t len; int graphdepth; int pipefd[2], rfd; int use_cumulative_counts; short cf, cb; uint64_t current_sampling_count; char *end, *tmp, *event; const char *errmsg, *graphfilename; enum pmcstat_state runstate; struct pmc_driverstats ds_start, ds_end; struct pmcstat_ev *ev; struct sigaction sa; struct kevent kev; struct winsize ws; struct stat sb; char buffer[PATH_MAX]; check_driver_stats = 0; current_sampling_count = 0; do_callchain = 1; do_descr = 0; do_descendants = 0; do_userspace = 0; do_logproccsw = 0; do_logprocexit = 0; do_listcounters = 0; use_cumulative_counts = 0; graphfilename = "-"; args.pa_required = 0; args.pa_flags = 0; args.pa_verbosity = 1; args.pa_logfd = -1; args.pa_fsroot = ""; args.pa_samplesdir = "."; args.pa_printfile = stderr; args.pa_graphdepth = DEFAULT_CALLGRAPH_DEPTH; args.pa_graphfile = NULL; args.pa_interval = DEFAULT_WAIT_INTERVAL; 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; args.pa_duration = 0.0; STAILQ_INIT(&args.pa_events); SLIST_INIT(&args.pa_targets); bzero(&ds_start, sizeof(ds_start)); bzero(&ds_end, sizeof(ds_end)); ev = NULL; event = NULL; CPU_ZERO(&cpumask); /* Default to using the running system kernel. */ len = 0; if (sysctlbyname("kern.bootfile", NULL, &len, NULL, 0) == -1) err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); args.pa_kernel = malloc(len); if (args.pa_kernel == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); if (sysctlbyname("kern.bootfile", args.pa_kernel, &len, NULL, 0) == -1) err(EX_OSERR, "ERROR: Cannot determine path of running kernel"); /* * The initial CPU mask specifies the root mask of this process * which is usually all CPUs in the system. */ if (cpuset_getaffinity(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, sizeof(rootmask), &rootmask) == -1) err(EX_OSERR, "ERROR: Cannot determine the root set of CPUs"); CPU_COPY(&rootmask, &cpumask); while ((option = getopt(argc, argv, - "CD:EF:G:ILM:NO:P:R:S:TUWZa:c:def:gi:k:l:m:n:o:p:qr:s:t:u:vw:z:")) != -1) + "ACD:EF:G:ILM:NO:P:R:S:TUWZa:c:def:gi:k:l:m:n:o:p:qr:s:t:u:vw:z:")) != -1) switch (option) { + case 'A': + args.pa_flags |= FLAG_SKIP_TOP_FN_RES; + break; + case 'a': /* Annotate + callgraph */ args.pa_flags |= FLAG_DO_ANNOTATE; args.pa_plugin = PMCSTAT_PL_ANNOTATE_CG; graphfilename = optarg; break; case 'C': /* cumulative values */ use_cumulative_counts = !use_cumulative_counts; args.pa_required |= FLAG_HAS_COUNTING_PMCS; break; case 'c': /* CPU */ if (optarg[0] == '*' && optarg[1] == '\0') CPU_COPY(&rootmask, &cpumask); else pmcstat_get_cpumask(optarg, &cpumask); args.pa_flags |= FLAGS_HAS_CPUMASK; args.pa_required |= FLAG_HAS_SYSTEM_PMCS; break; case 'D': if (stat(optarg, &sb) < 0) err(EX_OSERR, "ERROR: Cannot stat \"%s\"", optarg); if (!S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\" is not a directory.", optarg); args.pa_samplesdir = optarg; args.pa_flags |= FLAG_HAS_SAMPLESDIR; args.pa_required |= FLAG_DO_GPROF; break; case 'd': /* toggle descendents */ do_descendants = !do_descendants; args.pa_required |= FLAG_HAS_PROCESS_PMCS; break; case 'E': /* log process exit */ do_logprocexit = !do_logprocexit; args.pa_required |= (FLAG_HAS_PROCESS_PMCS | FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); break; case 'e': /* wide gprof metrics */ args.pa_flags |= FLAG_DO_WIDE_GPROF_HC; 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 'I': - args.pa_flags |= FLAG_SKIP_TOP_FN_RES; - break; case 'i': args.pa_flags |= FLAG_FILTER_THREAD_ID; args.pa_tid = strtol(optarg, &end, 0); + break; + + case 'I': + args.pa_flags |= FLAG_SHOW_OFFSET; break; case 'k': /* pathname to the kernel */ free(args.pa_kernel); args.pa_kernel = strdup(optarg); if (args.pa_kernel == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory"); args.pa_required |= FLAG_DO_ANALYSIS; args.pa_flags |= FLAG_HAS_KERNELPATH; break; case 'L': do_listcounters = 1; break; case 'l': /* time duration in seconds */ duration = strtod(optarg, &end); if (*end != '\0' || duration <= 0) errx(EX_USAGE, "ERROR: Illegal duration time " "value \"%s\".", optarg); args.pa_flags |= FLAG_HAS_DURATION; args.pa_duration = duration; break; case 'm': args.pa_flags |= FLAG_DO_ANNOTATE; args.pa_plugin = PMCSTAT_PL_ANNOTATE; graphfilename = optarg; break; case 'M': /* mapfile */ args.pa_mapfilename = optarg; break; case 'N': do_callchain = !do_callchain; args.pa_required |= FLAG_HAS_SAMPLING_PMCS; break; case 'p': /* process virtual counting PMC */ case 's': /* system-wide counting PMC */ case 'P': /* process virtual sampling PMC */ case 'S': /* system-wide sampling PMC */ if ((ev = malloc(sizeof(*ev))) == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); switch (option) { case 'p': ev->ev_mode = PMC_MODE_TC; break; case 's': ev->ev_mode = PMC_MODE_SC; break; case 'P': ev->ev_mode = PMC_MODE_TS; break; case 'S': ev->ev_mode = PMC_MODE_SS; break; } if (option == 'P' || option == 'p') { args.pa_flags |= FLAG_HAS_PROCESS_PMCS; args.pa_required |= (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET); } if (option == 'P' || option == 'S') { args.pa_flags |= FLAG_HAS_SAMPLING_PMCS; args.pa_required |= (FLAG_HAS_PIPE | FLAG_HAS_OUTPUT_LOGFILE); } if (option == 'p' || option == 's') args.pa_flags |= FLAG_HAS_COUNTING_PMCS; if (option == 's' || option == 'S') args.pa_flags |= FLAG_HAS_SYSTEM_PMCS; ev->ev_spec = strdup(optarg); if (ev->ev_spec == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); if (option == 'S' || option == 'P') ev->ev_count = current_sampling_count ? current_sampling_count : pmc_pmu_sample_rate_get(ev->ev_spec); else ev->ev_count = -1; if (option == 'S' || option == 's') ev->ev_cpu = CPU_FFS(&cpumask) - 1; else ev->ev_cpu = PMC_CPU_ANY; ev->ev_flags = 0; if (do_callchain) { ev->ev_flags |= PMC_F_CALLCHAIN; if (do_userspace) ev->ev_flags |= PMC_F_USERCALLCHAIN; } if (do_descendants) ev->ev_flags |= PMC_F_DESCENDANTS; if (do_logprocexit) ev->ev_flags |= PMC_F_LOG_PROCEXIT; if (do_logproccsw) ev->ev_flags |= PMC_F_LOG_PROCCSW; ev->ev_cumulative = use_cumulative_counts; ev->ev_saved = 0LL; ev->ev_pmcid = PMC_ID_INVALID; /* extract event name */ c = strcspn(optarg, ", \t"); ev->ev_name = malloc(c + 1); if (ev->ev_name == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory."); (void) strncpy(ev->ev_name, optarg, c); *(ev->ev_name + c) = '\0'; STAILQ_INSERT_TAIL(&args.pa_events, ev, ev_next); if (option == 's' || option == 'S') { CPU_CLR(ev->ev_cpu, &cpumask); pmcstat_clone_event_descriptor(ev, &cpumask, &args); CPU_SET(ev->ev_cpu, &cpumask); } break; case 'n': /* sampling count */ current_sampling_count = strtol(optarg, &end, 0); if (*end != '\0' || current_sampling_count <= 0) errx(EX_USAGE, "ERROR: Illegal count value \"%s\".", optarg); args.pa_required |= FLAG_HAS_SAMPLING_PMCS; break; case 'o': /* outputfile */ if (args.pa_printfile != NULL && args.pa_printfile != stdout && args.pa_printfile != stderr) (void) fclose(args.pa_printfile); if ((args.pa_printfile = fopen(optarg, "w")) == NULL) errx(EX_OSERR, "ERROR: cannot open \"%s\" for writing.", optarg); args.pa_flags |= FLAG_DO_PRINT; break; case 'O': /* sampling output */ if (args.pa_outputpath) errx(EX_USAGE, "ERROR: option -O may only be specified once."); args.pa_outputpath = optarg; args.pa_flags |= FLAG_HAS_OUTPUT_LOGFILE; break; case 'q': /* quiet mode */ args.pa_verbosity = 0; break; case 'r': /* root FS path */ args.pa_fsroot = optarg; break; case 'R': /* read an existing log file */ if (args.pa_inputpath != NULL) errx(EX_USAGE, "ERROR: option -R may only be specified once."); args.pa_inputpath = optarg; if (args.pa_printfile == stderr) args.pa_printfile = stdout; args.pa_flags |= FLAG_READ_LOGFILE; break; case 't': /* target pid or process name */ 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 'u': do_descr = 1; event = optarg; break; case 'U': /* toggle user-space callchain capture */ do_userspace = !do_userspace; args.pa_required |= FLAG_HAS_SAMPLING_PMCS; break; case 'v': /* verbose */ args.pa_verbosity++; break; case 'w': /* wait interval */ interval = strtod(optarg, &end); if (*end != '\0' || interval <= 0) errx(EX_USAGE, "ERROR: Illegal wait interval value \"%s\".", optarg); args.pa_flags |= FLAG_HAS_WAIT_INTERVAL; args.pa_interval = interval; break; case 'W': /* toggle LOG_CSW */ do_logproccsw = !do_logproccsw; args.pa_required |= (FLAG_HAS_PROCESS_PMCS | FLAG_HAS_COUNTING_PMCS | FLAG_HAS_OUTPUT_LOGFILE); break; case 'z': graphdepth = strtod(optarg, &end); if (*end != '\0' || graphdepth <= 0) errx(EX_USAGE, "ERROR: Illegal callchain depth \"%s\".", optarg); args.pa_graphdepth = graphdepth; args.pa_required |= FLAG_DO_CALLGRAPHS; break; case '?': default: pmcstat_show_usage(); break; } if ((do_listcounters | do_descr) && pmc_pmu_enabled() == 0) errx(EX_USAGE, "pmu features not supported on host or hwpmc not loaded"); if (do_listcounters) { pmc_pmu_print_counters(NULL); } else if (do_descr) { pmc_pmu_print_counter_desc(event); } if (do_listcounters | do_descr) exit(0); args.pa_argc = (argc -= optind); args.pa_argv = (argv += optind); /* If we read from logfile and no specified CPU mask use * the maximum CPU count. */ if ((args.pa_flags & FLAG_READ_LOGFILE) && (args.pa_flags & FLAGS_HAS_CPUMASK) == 0) CPU_FILL(&cpumask); args.pa_cpumask = cpumask; /* For selecting CPUs using -R. */ if (argc) /* command line present */ args.pa_flags |= FLAG_HAS_COMMANDLINE; if (args.pa_flags & (FLAG_DO_GPROF | FLAG_DO_CALLGRAPHS | FLAG_DO_ANNOTATE | FLAG_DO_TOP)) args.pa_flags |= FLAG_DO_ANALYSIS; /* * Check invocation syntax. */ /* disallow -O and -R together */ if (args.pa_outputpath && args.pa_inputpath) errx(EX_USAGE, "ERROR: options -O and -R are mutually exclusive."); /* disallow -T and -l together */ if ((args.pa_flags & FLAG_HAS_DURATION) && (args.pa_flags & FLAG_DO_TOP)) errx(EX_USAGE, "ERROR: options -T and -l are mutually " "exclusive."); /* -a and -m require -R */ if (args.pa_flags & FLAG_DO_ANNOTATE && args.pa_inputpath == NULL) errx(EX_USAGE, "ERROR: option %s requires an input file", args.pa_plugin == PMCSTAT_PL_ANNOTATE ? "-m" : "-a"); /* -m option is not allowed combined with -g or -G. */ 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"); if (args.pa_flags & FLAG_READ_LOGFILE) { errmsg = NULL; if (args.pa_flags & FLAG_HAS_COMMANDLINE) errmsg = "a command line specification"; else if (args.pa_flags & FLAG_HAS_TARGET) errmsg = "option -t"; else if (!STAILQ_EMPTY(&args.pa_events)) errmsg = "a PMC event specification"; if (errmsg) errx(EX_USAGE, "ERROR: option -R may not be used with %s.", errmsg); } else if (STAILQ_EMPTY(&args.pa_events)) /* All other uses require a PMC spec. */ pmcstat_show_usage(); /* check for -t pid without a process PMC spec */ if ((args.pa_required & FLAG_HAS_TARGET) && (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) errx(EX_USAGE, "ERROR: option -t requires a process mode PMC to be specified." ); /* check for process-mode options without a command or -t pid */ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) errx(EX_USAGE, "ERROR: options -d, -E, -p, -P, and -W require a command line or target process." ); /* check for -p | -P without a target process of some sort */ if ((args.pa_required & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) && (args.pa_flags & (FLAG_HAS_COMMANDLINE | FLAG_HAS_TARGET)) == 0) errx(EX_USAGE, "ERROR: options -P and -p require a target process or a command line." ); /* check for process-mode options without a process-mode PMC */ if ((args.pa_required & FLAG_HAS_PROCESS_PMCS) && (args.pa_flags & FLAG_HAS_PROCESS_PMCS) == 0) errx(EX_USAGE, "ERROR: options -d, -E, and -W require a process mode PMC to be specified." ); /* check for -c cpu with no system mode PMCs or logfile. */ if ((args.pa_required & FLAG_HAS_SYSTEM_PMCS) && (args.pa_flags & FLAG_HAS_SYSTEM_PMCS) == 0 && (args.pa_flags & FLAG_READ_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -c requires at least one system mode PMC to be specified." ); /* 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 and -o require at least one counting mode PMC to be specified." ); /* check for sampling mode options without a sampling PMC spec */ if ((args.pa_required & FLAG_HAS_SAMPLING_PMCS) && (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) == 0) errx(EX_USAGE, "ERROR: options -N, -n and -O require at least one sampling mode PMC to be specified." ); /* 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/-m/-T require sampling PMCs or -R to be specified." ); /* check if -e was specified without -g */ if ((args.pa_flags & FLAG_DO_WIDE_GPROF_HC) && !(args.pa_flags & FLAG_DO_GPROF)) errx(EX_USAGE, "ERROR: option -e requires gprof mode to be specified." ); /* check if -O was spuriously specified */ if ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) && (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -O is used only with options -E, -P, -S and -W." ); /* -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/-m/-T."); /* -D only applies to gprof output mode (-g) */ if ((args.pa_flags & FLAG_HAS_SAMPLESDIR) && (args.pa_flags & FLAG_DO_GPROF) == 0) errx(EX_USAGE, "ERROR: option -D is only used with -g."); /* -M mapfile requires -g or -R */ if (args.pa_mapfilename != NULL && (args.pa_flags & FLAG_DO_GPROF) == 0 && (args.pa_flags & FLAG_READ_LOGFILE) == 0) errx(EX_USAGE, "ERROR: option -M is only used with -g/-R."); /* * Disallow textual output of sampling PMCs if counting PMCs * have also been asked for, mostly because the combined output * is difficult to make sense of. */ if ((args.pa_flags & FLAG_HAS_COUNTING_PMCS) && (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && ((args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE) == 0)) errx(EX_USAGE, "ERROR: option -O is required if counting and sampling PMCs are specified together." ); /* * Check if 'kerneldir' refers to a file rather than a * directory. If so, use `dirname path` to determine the * kernel directory. */ (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, args.pa_kernel); if (stat(buffer, &sb) < 0) err(EX_OSERR, "ERROR: Cannot locate kernel \"%s\"", buffer); if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\": Unsupported file type.", buffer); if (!S_ISDIR(sb.st_mode)) { tmp = args.pa_kernel; args.pa_kernel = strdup(dirname(args.pa_kernel)); if (args.pa_kernel == NULL) errx(EX_SOFTWARE, "ERROR: Out of memory"); free(tmp); (void) snprintf(buffer, sizeof(buffer), "%s%s", args.pa_fsroot, args.pa_kernel); if (stat(buffer, &sb) < 0) err(EX_OSERR, "ERROR: Cannot stat \"%s\"", buffer); if (!S_ISDIR(sb.st_mode)) errx(EX_USAGE, "ERROR: \"%s\" is not a directory.", buffer); } /* * If we have a callgraph be created, select the outputfile. */ if (args.pa_flags & FLAG_DO_CALLGRAPHS) { if (strcmp(graphfilename, "-") == 0) args.pa_graphfile = args.pa_printfile; else { args.pa_graphfile = fopen(graphfilename, "w"); if (args.pa_graphfile == NULL) err(EX_OSERR, "ERROR: cannot open \"%s\" for writing", graphfilename); } } 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", graphfilename); } /* if we've been asked to process a log file, skip init */ if ((args.pa_flags & FLAG_READ_LOGFILE) == 0) { if (pmc_init() < 0) err(EX_UNAVAILABLE, "ERROR: Initialization of the pmc(3) library failed" ); if ((npmc = pmc_npmc(0)) < 0) /* assume all CPUs are identical */ err(EX_OSERR, "ERROR: Cannot determine the number of PMCs on CPU %d", 0); } /* Allocate a kqueue */ if ((pmcstat_kq = kqueue()) < 0) err(EX_OSERR, "ERROR: Cannot allocate kqueue"); /* Setup the logfile as the source. */ if (args.pa_flags & FLAG_READ_LOGFILE) { /* * Print the log in textual form if we haven't been * asked to generate profiling information. */ if ((args.pa_flags & FLAG_DO_ANALYSIS) == 0) args.pa_flags |= FLAG_DO_PRINT; pmcstat_log_initialize_logging(); rfd = pmcstat_open_log(args.pa_inputpath, PMCSTAT_OPEN_FOR_READ); if ((args.pa_logparser = pmclog_open(rfd)) == NULL) err(EX_OSERR, "ERROR: Cannot create parser"); if (fcntl(rfd, F_SETFL, O_NONBLOCK) < 0) err(EX_OSERR, "ERROR: fcntl(2) failed"); EV_SET(&kev, rfd, EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent"); } /* * Configure the specified log file or setup a default log * consumer via a pipe. */ if (args.pa_required & FLAG_HAS_OUTPUT_LOGFILE) { if (args.pa_outputpath) args.pa_logfd = pmcstat_open_log(args.pa_outputpath, PMCSTAT_OPEN_FOR_WRITE); else { /* * process the log on the fly by reading it in * through a pipe. */ if (pipe(pipefd) < 0) err(EX_OSERR, "ERROR: pipe(2) failed"); if (fcntl(pipefd[READPIPEFD], F_SETFL, O_NONBLOCK) < 0) err(EX_OSERR, "ERROR: fcntl(2) failed"); EV_SET(&kev, pipefd[READPIPEFD], EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent"); args.pa_logfd = pipefd[WRITEPIPEFD]; 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]); } if (pmc_configure_logfile(args.pa_logfd) < 0) err(EX_OSERR, "ERROR: Cannot configure log file"); } /* remember to check for driver errors if we are sampling or logging */ check_driver_stats = (args.pa_flags & FLAG_HAS_SAMPLING_PMCS) || (args.pa_flags & FLAG_HAS_OUTPUT_LOGFILE); /* if (args.pa_flags & FLAG_READ_LOGFILE) { * Allocate PMCs. */ STAILQ_FOREACH(ev, &args.pa_events, ev_next) { if (pmc_allocate(ev->ev_spec, ev->ev_mode, ev->ev_flags, ev->ev_cpu, &ev->ev_pmcid, ev->ev_count) < 0) err(EX_OSERR, "ERROR: Cannot allocate %s-mode pmc with specification \"%s\"", PMC_IS_SYSTEM_MODE(ev->ev_mode) ? "system" : "process", ev->ev_spec); if (PMC_IS_SAMPLING_MODE(ev->ev_mode) && pmc_set(ev->ev_pmcid, ev->ev_count) < 0) err(EX_OSERR, "ERROR: Cannot set sampling count for PMC \"%s\"", ev->ev_name); } /* compute printout widths */ STAILQ_FOREACH(ev, &args.pa_events, ev_next) { int counter_width; int display_width; int header_width; (void) pmc_width(ev->ev_pmcid, &counter_width); header_width = strlen(ev->ev_name) + 2; /* prefix '%c/' */ display_width = (int) floor(counter_width / 3.32193) + 1; if (PMC_IS_SYSTEM_MODE(ev->ev_mode)) header_width += 3; /* 2 digit CPU number + '/' */ if (header_width > display_width) { ev->ev_fieldskip = 0; ev->ev_fieldwidth = header_width; } else { ev->ev_fieldskip = display_width - header_width; ev->ev_fieldwidth = display_width; } } /* * If our output is being set to a terminal, register a handler * for window size changes. */ if (isatty(fileno(args.pa_printfile))) { if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) 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); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for SIGINT"); EV_SET(&kev, SIGIO, 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 SIGIO"); /* * An exec() failure of a forked child is signalled by the * child sending the parent a SIGCHLD. We don't register an * actual signal handler for SIGCHLD, but instead use our * kqueue to pick up the signal. */ EV_SET(&kev, SIGCHLD, 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 SIGCHLD"); /* * 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); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for timer"); } /* * Setup a duration timer if we have sampling mode PMCs and * a duration time is set */ if ((args.pa_flags & FLAG_HAS_SAMPLING_PMCS) && (args.pa_flags & FLAG_HAS_DURATION)) { EV_SET(&kev, 0, EVFILT_TIMER, EV_ADD, 0, args.pa_duration * 1000, NULL); if (kevent(pmcstat_kq, &kev, 1, NULL, 0, NULL) < 0) err(EX_OSERR, "ERROR: Cannot register kevent for " "time duration"); } /* attach PMCs to the target process, starting it if specified */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_create_process(pmcstat_sockpair, &args, pmcstat_kq); if (check_driver_stats && pmc_get_driver_stats(&ds_start) < 0) err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); /* Attach process pmcs to the target process. */ if (args.pa_flags & (FLAG_HAS_TARGET | FLAG_HAS_COMMANDLINE)) { if (SLIST_EMPTY(&args.pa_targets)) errx(EX_DATAERR, "ERROR: No matching target processes."); if (args.pa_flags & FLAG_HAS_PROCESS_PMCS) pmcstat_attach_pmcs(&args); if (pmcstat_kvm) { kvm_close(pmcstat_kvm); pmcstat_kvm = NULL; } } /* start the pmcs */ pmcstat_start_pmcs(); /* start the (commandline) process if needed */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_start_process(pmcstat_sockpair); /* initialize logging */ pmcstat_log_initialize_logging(); /* Handle SIGINT using the kqueue loop */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; (void) sigemptyset(&sa.sa_mask); 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(); /* Get terminal width / height with ncurses. */ getmaxyx(stdscr, pmcstat_displayheight, pmcstat_displaywidth); pmcstat_displayheight--; pmcstat_displaywidth--; atexit(pmcstat_topexit); } } /* * loop till either the target process (if any) exits, or we * are killed by a SIGINT or we reached the time duration. */ runstate = PMCSTAT_RUNNING; do_print = do_read = 0; do { if ((c = kevent(pmcstat_kq, NULL, 0, &kev, 1, NULL)) <= 0) { if (errno != EINTR) err(EX_OSERR, "ERROR: kevent failed"); else continue; } if (kev.flags & EV_ERROR) errc(EX_OSERR, kev.data, "ERROR: kevent failed"); switch (kev.filter) { case EVFILT_PROC: /* target has exited */ runstate = pmcstat_close_log(&args); do_print = 1; break; case EVFILT_READ: /* log file data is present */ if (kev.ident == (unsigned)fileno(stdin) && (args.pa_flags & FLAG_DO_TOP)) { if (pmcstat_keypress_log()) runstate = pmcstat_close_log(&args); } else { do_read = 0; runstate = pmcstat_process_log(); } break; case EVFILT_SIGNAL: if (kev.ident == SIGCHLD) { /* * The child process sends us a * SIGCHLD if its exec() failed. We * wait for it to exit and then exit * ourselves. */ (void) wait(&c); runstate = PMCSTAT_FINISHED; } else if (kev.ident == SIGIO) { /* * We get a SIGIO if a PMC loses all * of its targets, or if logfile * writes encounter an error. */ runstate = pmcstat_close_log(&args); do_print = 1; /* print PMCs at exit */ } else if (kev.ident == SIGINT) { /* Kill the child process if we started it */ if (args.pa_flags & FLAG_HAS_COMMANDLINE) pmcstat_kill_process(); runstate = pmcstat_close_log(&args); } else if (kev.ident == SIGWINCH) { if (ioctl(fileno(args.pa_printfile), TIOCGWINSZ, &ws) < 0) err(EX_OSERR, "ERROR: Cannot determine window size"); pmcstat_displayheight = ws.ws_row - 1; pmcstat_displaywidth = ws.ws_col - 1; } else assert(0); break; case EVFILT_TIMER: /* time duration reached, exit */ if (args.pa_flags & FLAG_HAS_DURATION) { runstate = PMCSTAT_FINISHED; break; } /* print out counting PMCs */ if ((args.pa_flags & FLAG_DO_TOP) && (args.pa_flags & FLAG_HAS_PIPE) && pmc_flush_logfile() == 0) do_read = 1; do_print = 1; break; } if (do_print && !do_read) { 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_close_logfile(); pmcstat_cleanup(); /* check if the driver lost any samples or events */ if (check_driver_stats) { if (pmc_get_driver_stats(&ds_end) < 0) err(EX_OSERR, "ERROR: Cannot retrieve driver statistics"); if (ds_start.pm_intr_bufferfull != ds_end.pm_intr_bufferfull && args.pa_verbosity > 0) warnx( "WARNING: sampling was paused at least %u time%s.\n" "Please consider tuning the \"kern.hwpmc.nsamples\" tunable.", ds_end.pm_intr_bufferfull - ds_start.pm_intr_bufferfull, ((ds_end.pm_intr_bufferfull - ds_start.pm_intr_bufferfull) != 1) ? "s" : "" ); if (ds_start.pm_buffer_requests_failed != ds_end.pm_buffer_requests_failed && args.pa_verbosity > 0) warnx( "WARNING: at least %u event%s were discarded while running.\n" "Please consider tuning the \"kern.hwpmc.nbuffers\" tunable.", ds_end.pm_buffer_requests_failed - ds_start.pm_buffer_requests_failed, ((ds_end.pm_buffer_requests_failed - ds_start.pm_buffer_requests_failed) != 1) ? "s" : "" ); } exit(EX_OK); } Index: head/usr.sbin/pmcstat/pmcstat_log.c =================================================================== --- head/usr.sbin/pmcstat/pmcstat_log.c (revision 363927) +++ head/usr.sbin/pmcstat/pmcstat_log.c (revision 363928) @@ -1,742 +1,754 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pmcstat.h" #include "pmcstat_log.h" #include "pmcstat_top.h" /* * PUBLIC INTERFACES * * pmcstat_initialize_logging() initialize this module, called first * pmcstat_shutdown_logging() orderly shutdown, called last * pmcstat_open_log() open an eventlog for processing * pmcstat_process_log() print/convert an event log * pmcstat_display_log() top mode display for the log * pmcstat_close_log() finish processing an event log * * IMPLEMENTATION NOTES * * We correlate each 'callchain' or 'sample' entry seen in the event * log back to an executable object in the system. Executable objects * include: * - program executables, * - shared libraries loaded by the runtime loader, * - dlopen()'ed objects loaded by the program, * - the runtime loader itself, * - the kernel and kernel modules. * * Each process that we know about is treated as a set of regions that * map to executable objects. Processes are described by * 'pmcstat_process' structures. Executable objects are tracked by * 'pmcstat_image' structures. The kernel and kernel modules are * common to all processes (they reside at the same virtual addresses * for all processes). Individual processes can have their text * segments and shared libraries loaded at process-specific locations. * * A given executable object can be in use by multiple processes * (e.g., libc.so) and loaded at a different address in each. * pmcstat_pcmap structures track per-image mappings. * * The sample log could have samples from multiple PMCs; we * generate one 'gmon.out' profile per PMC. * * IMPLEMENTATION OF GMON OUTPUT * * Each executable object gets one 'gmon.out' profile, per PMC in * use. Creation of 'gmon.out' profiles is done lazily. The * 'gmon.out' profiles generated for a given sampling PMC are * aggregates of all the samples for that particular executable * object. * * IMPLEMENTATION OF SYSTEM-WIDE CALLGRAPH OUTPUT * * Each active pmcid has its own callgraph structure, described by a * 'struct pmcstat_callgraph'. Given a process id and a list of pc * values, we map each pc value to a tuple (image, symbol), where * 'image' denotes an executable object and 'symbol' is the closest * symbol that precedes the pc value. Each pc value in the list is * also given a 'rank' that reflects its depth in the call stack. */ struct pmcstat_pmcs pmcstat_pmcs = LIST_HEAD_INITIALIZER(pmcstat_pmcs); /* * All image descriptors are kept in a hash table. */ struct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH]; /* * All process descriptors are kept in a hash table. */ struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH]; struct pmcstat_stats pmcstat_stats; /* statistics */ static int ps_samples_period; /* samples count between top refresh. */ struct pmcstat_process *pmcstat_kernproc; /* kernel 'process' */ #include "pmcpl_gprof.h" #include "pmcpl_callgraph.h" #include "pmcpl_annotate.h" #include "pmcpl_annotate_cg.h" #include "pmcpl_calltree.h" static struct pmc_plugins plugins[] = { { .pl_name = "none", }, { .pl_name = "callgraph", .pl_init = pmcpl_cg_init, .pl_shutdown = pmcpl_cg_shutdown, .pl_process = pmcpl_cg_process, .pl_topkeypress = pmcpl_cg_topkeypress, .pl_topdisplay = pmcpl_cg_topdisplay }, { .pl_name = "gprof", .pl_shutdown = pmcpl_gmon_shutdown, .pl_process = pmcpl_gmon_process, .pl_initimage = pmcpl_gmon_initimage, .pl_shutdownimage = pmcpl_gmon_shutdownimage, .pl_newpmc = pmcpl_gmon_newpmc }, { .pl_name = "annotate", .pl_process = pmcpl_annotate_process }, { .pl_name = "calltree", .pl_configure = pmcpl_ct_configure, .pl_init = pmcpl_ct_init, .pl_shutdown = pmcpl_ct_shutdown, .pl_process = pmcpl_ct_process, .pl_topkeypress = pmcpl_ct_topkeypress, .pl_topdisplay = pmcpl_ct_topdisplay }, { .pl_name = "annotate_cg", .pl_process = pmcpl_annotate_cg_process }, { .pl_name = NULL } }; static int pmcstat_mergepmc; int pmcstat_pmcinfilter = 0; /* PMC filter for top mode. */ float pmcstat_threshold = 0.5; /* Cost filter for top mode. */ /* * Prototypes */ static void pmcstat_stats_reset(int _reset_global); /* * PMC count. */ int pmcstat_npmcs; /* * PMC Top mode pause state. */ static int pmcstat_pause; static void pmcstat_stats_reset(int reset_global) { struct pmcstat_pmcrecord *pr; /* Flush PMCs stats. */ LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) { pr->pr_samples = 0; pr->pr_dubious_frames = 0; } ps_samples_period = 0; /* Flush global stats. */ if (reset_global) bzero(&pmcstat_stats, sizeof(struct pmcstat_stats)); } /* * Resolve file name and line number for the given address. */ 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) { static int addr2line_warn = 0; char *sep, cmdline[PATH_MAX], imagepath[PATH_MAX]; unsigned l; int fd; if (image->pi_addr2line == NULL) { /* Try default debug file location. */ snprintf(imagepath, sizeof(imagepath), "/usr/lib/debug/%s%s.debug", args.pa_fsroot, pmcstat_string_unintern(image->pi_fullpath)); fd = open(imagepath, O_RDONLY); if (fd < 0) { /* Old kernel symbol path. */ snprintf(imagepath, sizeof(imagepath), "%s%s.symbols", args.pa_fsroot, pmcstat_string_unintern(image->pi_fullpath)); fd = open(imagepath, O_RDONLY); if (fd < 0) { snprintf(imagepath, sizeof(imagepath), "%s%s", args.pa_fsroot, pmcstat_string_unintern( image->pi_fullpath)); } } if (fd >= 0) close(fd); /* * New addr2line support recursive inline function with -i * but the format does not add a marker when no more entries * are available. */ snprintf(cmdline, sizeof(cmdline), "addr2line -Cfe \"%s\"", imagepath); image->pi_addr2line = popen(cmdline, "r+"); if (image->pi_addr2line == NULL) { if (!addr2line_warn) { addr2line_warn = 1; warnx( "WARNING: addr2line is needed for source code information." ); } return (0); } } if (feof(image->pi_addr2line) || ferror(image->pi_addr2line)) { warnx("WARNING: addr2line pipe error"); pclose(image->pi_addr2line); image->pi_addr2line = NULL; return (0); } fprintf(image->pi_addr2line, "%p\n", (void *)addr); if (fgets(funcname, funcname_len, image->pi_addr2line) == NULL) { warnx("WARNING: addr2line function name read error"); return (0); } sep = strchr(funcname, '\n'); if (sep != NULL) *sep = '\0'; if (fgets(sourcefile, sourcefile_len, image->pi_addr2line) == NULL) { warnx("WARNING: addr2line source file read error"); return (0); } sep = strchr(sourcefile, ':'); if (sep == NULL) { warnx("WARNING: addr2line source line separator missing"); return (0); } *sep = '\0'; l = atoi(sep+1); if (l == 0) return (0); *sourceline = l; return (1); } /* * Given a pmcid in use, find its human-readable name. */ const char * pmcstat_pmcid_to_name(pmc_id_t pmcid) { struct pmcstat_pmcrecord *pr; LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) if (pr->pr_pmcid == pmcid) return (pmcstat_string_unintern(pr->pr_pmcname)); return NULL; } /* * Convert PMC index to name. */ const char * pmcstat_pmcindex_to_name(int pmcin) { struct pmcstat_pmcrecord *pr; LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) if (pr->pr_pmcin == pmcin) return pmcstat_string_unintern(pr->pr_pmcname); return NULL; } /* * Return PMC record with given index. */ struct pmcstat_pmcrecord * pmcstat_pmcindex_to_pmcr(int pmcin) { struct pmcstat_pmcrecord *pr; LIST_FOREACH(pr, &pmcstat_pmcs, pr_next) if (pr->pr_pmcin == pmcin) return pr; return NULL; } /* * Print log entries as text. */ static int pmcstat_print_log(void) { struct pmclog_ev ev; uint32_t npc; while (pmclog_read(args.pa_logparser, &ev) == 0) { assert(ev.pl_state == PMCLOG_OK); switch (ev.pl_type) { case PMCLOG_TYPE_CALLCHAIN: PMCSTAT_PRINT_ENTRY("callchain", "%d 0x%x %d %d %c", ev.pl_u.pl_cc.pl_pid, ev.pl_u.pl_cc.pl_pmcid, PMC_CALLCHAIN_CPUFLAGS_TO_CPU(ev.pl_u.pl_cc. \ pl_cpuflags), ev.pl_u.pl_cc.pl_npc, PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(ev.pl_u.pl_cc.\ pl_cpuflags) ? 'u' : 's'); for (npc = 0; npc < ev.pl_u.pl_cc.pl_npc; npc++) PMCSTAT_PRINT_ENTRY("...", "%p", (void *) ev.pl_u.pl_cc.pl_pc[npc]); break; case PMCLOG_TYPE_CLOSELOG: PMCSTAT_PRINT_ENTRY("closelog",); break; case PMCLOG_TYPE_DROPNOTIFY: PMCSTAT_PRINT_ENTRY("drop",); break; case PMCLOG_TYPE_INITIALIZE: PMCSTAT_PRINT_ENTRY("initlog","0x%x \"%s\"", ev.pl_u.pl_i.pl_version, pmc_name_of_cputype(ev.pl_u.pl_i.pl_arch)); if ((ev.pl_u.pl_i.pl_version & 0xFF000000) != PMC_VERSION_MAJOR << 24) warnx( "WARNING: Log version 0x%x != expected version 0x%x.", ev.pl_u.pl_i.pl_version, PMC_VERSION); break; case PMCLOG_TYPE_MAP_IN: PMCSTAT_PRINT_ENTRY("map-in","%d %p \"%s\"", ev.pl_u.pl_mi.pl_pid, (void *) ev.pl_u.pl_mi.pl_start, ev.pl_u.pl_mi.pl_pathname); break; case PMCLOG_TYPE_MAP_OUT: PMCSTAT_PRINT_ENTRY("map-out","%d %p %p", ev.pl_u.pl_mo.pl_pid, (void *) ev.pl_u.pl_mo.pl_start, (void *) ev.pl_u.pl_mo.pl_end); break; case PMCLOG_TYPE_PMCALLOCATE: PMCSTAT_PRINT_ENTRY("allocate","0x%x \"%s\" 0x%x", ev.pl_u.pl_a.pl_pmcid, ev.pl_u.pl_a.pl_evname, ev.pl_u.pl_a.pl_flags); break; case PMCLOG_TYPE_PMCALLOCATEDYN: PMCSTAT_PRINT_ENTRY("allocatedyn","0x%x \"%s\" 0x%x", ev.pl_u.pl_ad.pl_pmcid, ev.pl_u.pl_ad.pl_evname, ev.pl_u.pl_ad.pl_flags); break; case PMCLOG_TYPE_PMCATTACH: PMCSTAT_PRINT_ENTRY("attach","0x%x %d \"%s\"", ev.pl_u.pl_t.pl_pmcid, ev.pl_u.pl_t.pl_pid, ev.pl_u.pl_t.pl_pathname); break; case PMCLOG_TYPE_PMCDETACH: PMCSTAT_PRINT_ENTRY("detach","0x%x %d", ev.pl_u.pl_d.pl_pmcid, ev.pl_u.pl_d.pl_pid); break; case PMCLOG_TYPE_PROCCSW: PMCSTAT_PRINT_ENTRY("cswval","0x%x %d %jd", ev.pl_u.pl_c.pl_pmcid, ev.pl_u.pl_c.pl_pid, ev.pl_u.pl_c.pl_value); break; case PMCLOG_TYPE_PROCEXEC: PMCSTAT_PRINT_ENTRY("exec","0x%x %d %p \"%s\"", ev.pl_u.pl_x.pl_pmcid, ev.pl_u.pl_x.pl_pid, (void *) ev.pl_u.pl_x.pl_entryaddr, ev.pl_u.pl_x.pl_pathname); break; case PMCLOG_TYPE_PROCEXIT: PMCSTAT_PRINT_ENTRY("exitval","0x%x %d %jd", ev.pl_u.pl_e.pl_pmcid, ev.pl_u.pl_e.pl_pid, ev.pl_u.pl_e.pl_value); break; case PMCLOG_TYPE_PROCFORK: PMCSTAT_PRINT_ENTRY("fork","%d %d", ev.pl_u.pl_f.pl_oldpid, ev.pl_u.pl_f.pl_newpid); break; case PMCLOG_TYPE_USERDATA: PMCSTAT_PRINT_ENTRY("userdata","0x%x", ev.pl_u.pl_u.pl_userdata); break; case PMCLOG_TYPE_SYSEXIT: PMCSTAT_PRINT_ENTRY("exit","%d", ev.pl_u.pl_se.pl_pid); break; default: fprintf(args.pa_printfile, "unknown event (type %d).\n", ev.pl_type); } } if (ev.pl_state == PMCLOG_EOF) return (PMCSTAT_FINISHED); else if (ev.pl_state == PMCLOG_REQUIRE_DATA) return (PMCSTAT_RUNNING); errx(EX_DATAERR, "ERROR: event parsing failed (record %jd, offset 0x%jx).", (uintmax_t) ev.pl_count + 1, ev.pl_offset); /*NOTREACHED*/ } /* * Public Interfaces. */ /* * Process a log file in offline analysis mode. */ int pmcstat_process_log(void) { /* * If analysis has not been asked for, just print the log to * the current output file. */ if (args.pa_flags & FLAG_DO_PRINT) return (pmcstat_print_log()); else return (pmcstat_analyze_log(&args, plugins, &pmcstat_stats, pmcstat_kernproc, pmcstat_mergepmc, &pmcstat_npmcs, &ps_samples_period)); } /* * Refresh top display. */ static void pmcstat_refresh_top(void) { int v_attrs; float v; char pmcname[40]; struct pmcstat_pmcrecord *pmcpr; /* If in pause mode do not refresh display. */ if (pmcstat_pause) return; /* Wait until PMC pop in the log. */ pmcpr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); if (pmcpr == NULL) return; /* Format PMC name. */ if (pmcstat_mergepmc) snprintf(pmcname, sizeof(pmcname), "[%s]", pmcstat_string_unintern(pmcpr->pr_pmcname)); else snprintf(pmcname, sizeof(pmcname), "%s.%d", pmcstat_string_unintern(pmcpr->pr_pmcname), pmcstat_pmcinfilter); /* Format samples count. */ if (ps_samples_period > 0) v = (pmcpr->pr_samples * 100.0) / ps_samples_period; else v = 0.; v_attrs = PMCSTAT_ATTRPERCENT(v); PMCSTAT_PRINTBEGIN(); PMCSTAT_PRINTW("PMC: %s Samples: %u ", pmcname, pmcpr->pr_samples); PMCSTAT_ATTRON(v_attrs); PMCSTAT_PRINTW("(%.1f%%) ", v); PMCSTAT_ATTROFF(v_attrs); PMCSTAT_PRINTW(", %u unresolved\n\n", pmcpr->pr_dubious_frames); if (plugins[args.pa_plugin].pl_topdisplay != NULL) plugins[args.pa_plugin].pl_topdisplay(); PMCSTAT_PRINTEND(); } /* * Find the next pmc index to display. */ static void pmcstat_changefilter(void) { int pmcin; struct pmcstat_pmcrecord *pmcr; /* * Find the next merge target. */ if (pmcstat_mergepmc) { pmcin = pmcstat_pmcinfilter; do { pmcr = pmcstat_pmcindex_to_pmcr(pmcstat_pmcinfilter); if (pmcr == NULL || pmcr == pmcr->pr_merge) break; pmcstat_pmcinfilter++; if (pmcstat_pmcinfilter >= pmcstat_npmcs) pmcstat_pmcinfilter = 0; } while (pmcstat_pmcinfilter != pmcin); } } /* * Top mode keypress. */ int pmcstat_keypress_log(void) { int c, ret = 0; WINDOW *w; w = newwin(1, 0, 1, 0); c = wgetch(w); wprintw(w, "Key: %c => ", c); switch (c) { + case 'A': + if (args.pa_flags & FLAG_SKIP_TOP_FN_RES) + args.pa_flags &= ~FLAG_SKIP_TOP_FN_RES; + else + args.pa_flags |= FLAG_SKIP_TOP_FN_RES; + break; case 'c': wprintw(w, "enter mode 'd' or 'a' => "); c = wgetch(w); if (c == 'd') { args.pa_topmode = PMCSTAT_TOP_DELTA; wprintw(w, "switching to delta mode"); } else { args.pa_topmode = PMCSTAT_TOP_ACCUM; wprintw(w, "switching to accumulation mode"); } + break; + case 'I': + if (args.pa_flags & FLAG_SHOW_OFFSET) + args.pa_flags &= ~FLAG_SHOW_OFFSET; + else + args.pa_flags |= FLAG_SHOW_OFFSET; break; case 'm': pmcstat_mergepmc = !pmcstat_mergepmc; /* * Changing merge state require data reset. */ if (plugins[args.pa_plugin].pl_shutdown != NULL) plugins[args.pa_plugin].pl_shutdown(NULL); pmcstat_stats_reset(0); if (plugins[args.pa_plugin].pl_init != NULL) plugins[args.pa_plugin].pl_init(); /* Update filter to be on a merge target. */ pmcstat_changefilter(); wprintw(w, "merge PMC %s", pmcstat_mergepmc ? "on" : "off"); break; case 'n': /* Close current plugin. */ if (plugins[args.pa_plugin].pl_shutdown != NULL) plugins[args.pa_plugin].pl_shutdown(NULL); /* Find next top display available. */ do { args.pa_plugin++; if (plugins[args.pa_plugin].pl_name == NULL) args.pa_plugin = 0; } while (plugins[args.pa_plugin].pl_topdisplay == NULL); /* Open new plugin. */ pmcstat_stats_reset(0); if (plugins[args.pa_plugin].pl_init != NULL) plugins[args.pa_plugin].pl_init(); wprintw(w, "switching to plugin %s", plugins[args.pa_plugin].pl_name); break; case 'p': pmcstat_pmcinfilter++; if (pmcstat_pmcinfilter >= pmcstat_npmcs) pmcstat_pmcinfilter = 0; pmcstat_changefilter(); wprintw(w, "switching to PMC %s.%d", pmcstat_pmcindex_to_name(pmcstat_pmcinfilter), pmcstat_pmcinfilter); break; case ' ': pmcstat_pause = !pmcstat_pause; if (pmcstat_pause) wprintw(w, "pause => press space again to continue"); break; case 'q': wprintw(w, "exiting..."); ret = 1; break; default: if (plugins[args.pa_plugin].pl_topkeypress != NULL) if (plugins[args.pa_plugin].pl_topkeypress(c, (void *)w)) ret = 1; } wrefresh(w); delwin(w); return ret; } /* * Top mode display. */ void pmcstat_display_log(void) { pmcstat_refresh_top(); /* Reset everythings if delta mode. */ if (args.pa_topmode == PMCSTAT_TOP_DELTA) { if (plugins[args.pa_plugin].pl_shutdown != NULL) plugins[args.pa_plugin].pl_shutdown(NULL); pmcstat_stats_reset(0); if (plugins[args.pa_plugin].pl_init != NULL) plugins[args.pa_plugin].pl_init(); } } /* * Configure a plugins. */ void pmcstat_pluginconfigure_log(char *opt) { if (strncmp(opt, "threshold=", 10) == 0) { pmcstat_threshold = atof(opt+10); } else { if (plugins[args.pa_plugin].pl_configure != NULL) { if (!plugins[args.pa_plugin].pl_configure(opt)) err(EX_USAGE, "ERROR: unknown option <%s>.", opt); } } } void pmcstat_log_shutdown_logging(void) { pmcstat_shutdown_logging(&args, plugins, &pmcstat_stats); } void pmcstat_log_initialize_logging(void) { pmcstat_initialize_logging(&pmcstat_kernproc, &args, plugins, &pmcstat_npmcs, &pmcstat_mergepmc); }