# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1999 by Sun Microsystems, Inc. # All rights reserved. # #ident "%Z%%M% %I% %E% SMI" include ../Makefile.cmd SUBDIRS= cputrack cpustat all : TARGET = all install : TARGET = install clean : TARGET = clean clobber : TARGET = clobber lint : TARGET = lint strip : TARGET = strip _msg : TARGET = _msg .KEEP_STATE: all: $(SUBDIRS) install strip clean clobber lint _msg: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #define __EXTENSIONS__ /* header bug! strtok_r is overly hidden */ #include #include #include #include #include #include #include #include "cpucmds.h" struct args { FILE *fp; int colnum; int margin; }; struct evlist { char *list; int size; }; #define MAX_RHS_COLUMN 76 #define EVENT_MARGIN 17 #define ATTR_MARGIN 20 /*ARGSUSED*/ static void list_cap(void *arg, uint_t regno, const char *name) { struct args *args = arg; int i; if ((args->colnum + strlen(name) + 1) > MAX_RHS_COLUMN) { (void) fprintf(args->fp, "\n"); for (i = 0; i < args->margin; i++) (void) fprintf(args->fp, " "); args->colnum = args->margin; } args->colnum += fprintf(args->fp, "%s ", name); } static void list_attr(void *arg, const char *name) { /* * The following attributes are used by the commands but should not be * reported to the user, since they may not be specified directly. */ if (strncmp(name, "picnum", 7) == 0 || strncmp(name, "count_sibling_usr", 18) == 0 || strncmp(name, "count_sibling_sys", 18) == 0) return; list_cap(arg, 0, name); } static void * emalloc(size_t size) { void *ptr; if ((ptr = malloc(size)) == NULL) { (void) fprintf(stderr, gettext("no memory available\n")); exit(1); } return (ptr); } /* * Used by allpics_equal(). */ /*ARGSUSED*/ static void cap_walker(void *arg, uint_t regno, const char *name) { struct evlist *list = arg; list->size += strlen(name); if ((list->list = realloc(list->list, list->size + 1)) == NULL) { (void) fprintf(stderr, gettext("no memory available\n")); exit(1); } (void) strcat(list->list, name); } /* * Returns 1 if all counters on this chip can count all possible events. */ static int allpics_equal(cpc_t *cpc) { int npics = cpc_npic(cpc); int i; struct evlist **lists; int ret = 1; lists = emalloc(npics * sizeof (struct evlist *)); for (i = 0; i < npics; i++) { lists[i] = emalloc(sizeof (struct evlist)); lists[i]->size = 0; lists[i]->list = emalloc(1); lists[i]->list[0] = '\0'; cpc_walk_events_pic(cpc, i, lists[i], cap_walker); } for (i = 1; i < npics; i++) if (lists[i]->size != lists[0]->size || strncmp(lists[i]->list, lists[0]->list, lists[0]->size) != 0) { ret = 0; break; } for (i = 0; i < npics; i++) { free(lists[i]->list); free(lists[i]); } free(lists); return (ret); } int capabilities(cpc_t *cpc, FILE *fp) { struct args _args, *args = &_args; char *text, *tok, *cp; const char *ccp; int npic = cpc_npic(cpc); int i, pics_equal = allpics_equal(cpc); args->fp = fp; if ((ccp = cpc_cciname(cpc)) == NULL) ccp = "No information available"; (void) fprintf(args->fp, "\t%s: %s\n\n", gettext("CPU performance counter interface"), ccp); (void) fprintf(args->fp, gettext("\tevent specification syntax:\n")); (void) fprintf(args->fp, "\t[picn=][,attr[n][=]]" "[,[picn=][,attr[n][=]],...]\n"); (void) fprintf(args->fp, gettext("\n\tGeneric Events:\n")); if (pics_equal) { args->margin = args->colnum = EVENT_MARGIN; (void) fprintf(args->fp, "\n\tevent[0-%d]: ", npic - 1); cpc_walk_generic_events_pic(cpc, 0, args, list_cap); (void) fprintf(args->fp, "\n"); } else { args->margin = EVENT_MARGIN; for (i = 0; i < npic; i++) { (void) fprintf(args->fp, "\n\tevent%d: ", i); if (i < 10) (void) fprintf(args->fp, " "); args->colnum = EVENT_MARGIN; cpc_walk_generic_events_pic(cpc, i, args, list_cap); (void) fprintf(args->fp, "\n"); } } (void) fprintf(args->fp, gettext("\n\tSee generic_events(3CPC) for" " descriptions of these events\n\n")); (void) fprintf(args->fp, gettext("\tPlatform Specific Events:\n")); if (pics_equal) { args->margin = args->colnum = EVENT_MARGIN; (void) fprintf(args->fp, "\n\tevent[0-%d]: ", npic - 1); cpc_walk_events_pic(cpc, 0, args, list_cap); (void) fprintf(args->fp, "\n"); } else { args->margin = EVENT_MARGIN; for (i = 0; i < npic; i++) { (void) fprintf(args->fp, "\n\tevent%d: ", i); if (i < 10) (void) fprintf(args->fp, " "); args->colnum = EVENT_MARGIN; cpc_walk_events_pic(cpc, i, args, list_cap); (void) fprintf(args->fp, "\n"); } } (void) fprintf(args->fp, "\n\tattributes: "); args->colnum = args->margin = ATTR_MARGIN; cpc_walk_attrs(cpc, args, list_attr); /* * In addition to the attributes published by the kernel, we allow the * user to specify two additional tokens on all platforms. List them * here. */ list_cap(args, 0, "nouser"); list_cap(args, 0, "sys"); (void) fprintf(args->fp, "\n\n\t"); args->colnum = 8; if ((ccp = cpc_cpuref(cpc)) == NULL) ccp = "No information available"; if ((text = strdup(ccp)) == NULL) { (void) fprintf(stderr, gettext("no memory available.\n")); exit(1); } for (cp = strtok_r(text, " ", &tok); cp != NULL; cp = strtok_r(NULL, " ", &tok)) { if ((args->colnum + strlen(cp) + 1) > MAX_RHS_COLUMN) { (void) fprintf(args->fp, "\n\t"); args->colnum = 8; } args->colnum += fprintf(args->fp, "%s ", cp); } (void) fprintf(args->fp, "\n"); free(text); return (0); } /* * Returns 1 on SMT processors which do not have full CPC hardware for each * logical processor. */ int smt_limited_cpc_hw(cpc_t *cpc) { if (strcmp(cpc_cciname(cpc), "Pentium 4 with HyperThreading") == 0) return (1); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _CPUCMDS_H #define _CPUCMDS_H #include #include #include #ifdef __cplusplus extern "C" { #endif #include "libcpc_impl.h" extern cpc_set_t *cpc_strtoset(cpc_t *cpc, const char *spec, int smt); extern cpc_errhndlr_t *strtoset_errfn; extern int capabilities(cpc_t *cpc, FILE *); extern int smt_limited_cpc_hw(cpc_t *cpc); extern void zerotime(void); extern float mstimestamp(hrtime_t hrt); /* * Request sets can be manipulated in collections called setgroups. */ typedef struct __cpc_setgrp cpc_setgrp_t; extern cpc_setgrp_t *cpc_setgrp_new(cpc_t *cpc, int smt); extern cpc_setgrp_t *cpc_setgrp_newset(cpc_setgrp_t *sgrp, const char *spec, int *errcnt); extern int cpc_setgrp_getbufs(cpc_setgrp_t *sgrp, cpc_buf_t ***data1, cpc_buf_t ***data2, cpc_buf_t ***scratch); extern cpc_setgrp_t *cpc_setgrp_clone(cpc_setgrp_t *sgrp); extern void cpc_setgrp_free(cpc_setgrp_t *sgrp); extern cpc_set_t *cpc_setgrp_getset(cpc_setgrp_t *sgrp); extern const char *cpc_setgrp_getname(cpc_setgrp_t *sgrp); extern const char *cpc_setgrp_gethdr(cpc_setgrp_t *sgrp); extern int cpc_setgrp_numsets(cpc_setgrp_t *sgrp); extern cpc_set_t *cpc_setgrp_nextset(cpc_setgrp_t *sgrp); extern void cpc_setgrp_reset(cpc_setgrp_t *to); extern void cpc_setgrp_accum(cpc_setgrp_t *accum, cpc_setgrp_t *sgrp); extern int cpc_setgrp_sysonly(cpc_setgrp_t *sgrp); extern int cpc_setgrp_has_sysonly(cpc_setgrp_t *sgrp); #ifdef __cplusplus } #endif #endif /* _CPUCMDS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpucmds.h" #include "statcommon.h" static struct options { int debug; int dotitle; int dohelp; int dotick; int dosoaker; int doperiod; char *pgmname; uint_t mseconds; uint_t nsamples; uint_t nsets; uint_t mseconds_rest; cpc_setgrp_t *master; } __options; /* * States for soaker threads. */ #define SOAK_PAUSE 0 #define SOAK_RUN 1 struct tstate { processorid_t cpuid; int chip_id; cpc_setgrp_t *sgrp; int status; thread_t tid; int soak_state; mutex_t soak_lock; cond_t soak_cv; }; static const struct options *opts = (const struct options *)&__options; static cpc_t *cpc; struct tstate *gstate; static int ncpus; static int max_chip_id; static int *chip_designees; /* cpuid of CPU which counts for phs chip */ static int smt = 0; /* If set, cpustat needs to be SMT-aware. */ static pcinfo_t fxinfo = { 0, "FX", 0 }; /* FX scheduler class info */ static uint_t timestamp_fmt = NODATE; /*ARGSUSED*/ static void cpustat_errfn(const char *fn, int subcode, const char *fmt, va_list ap) { (void) fprintf(stderr, "%s: ", opts->pgmname); if (opts->debug) (void) fprintf(stderr, "%s: ", fn); (void) vfprintf(stderr, fmt, ap); } static int cpustat(void); static int get_chipid(kstat_ctl_t *kc, processorid_t cpuid); static void *soaker(void *arg); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif int main(int argc, char *argv[]) { struct options *opts = &__options; int c, errcnt = 0, ret; cpc_setgrp_t *sgrp; char *errstr; double period; char *endp; struct rlimit rl; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if ((opts->pgmname = strrchr(argv[0], '/')) == NULL) opts->pgmname = argv[0]; else opts->pgmname++; /* Make sure we can open enough files */ rl.rlim_max = rl.rlim_cur = RLIM_INFINITY; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: setrlimit failed - %s\n"), opts->pgmname, errstr); } if ((cpc = cpc_open(CPC_VER_CURRENT)) == NULL) { errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: cannot access performance " "counters - %s\n"), opts->pgmname, errstr); return (1); } (void) cpc_seterrhndlr(cpc, cpustat_errfn); strtoset_errfn = cpustat_errfn; /* * Check to see if cpustat needs to be SMT-aware. */ smt = smt_limited_cpc_hw(cpc); /* * Establish some defaults */ opts->mseconds = 5000; opts->nsamples = UINT_MAX; opts->dotitle = 1; if ((opts->master = cpc_setgrp_new(cpc, smt)) == NULL) { (void) fprintf(stderr, gettext("%s: out of heap\n"), opts->pgmname); return (1); } while ((c = getopt(argc, argv, "Dc:hntT:sp:")) != EOF && errcnt == 0) switch (c) { case 'D': /* enable debugging */ opts->debug++; break; case 'c': /* specify statistics */ if ((sgrp = cpc_setgrp_newset(opts->master, optarg, &errcnt)) != NULL) opts->master = sgrp; break; case 'n': /* no titles */ opts->dotitle = 0; break; case 'p': /* periodic behavior */ opts->doperiod = 1; period = strtod(optarg, &endp); if (*endp != '\0') { (void) fprintf(stderr, gettext("%s: invalid " "parameter \"%s\"\n"), opts->pgmname, optarg); errcnt++; } break; case 's': /* run soaker thread */ opts->dosoaker = 1; break; case 't': /* print %tick */ opts->dotick = 1; break; case 'T': if (optarg) { if (*optarg == 'u') timestamp_fmt = UDATE; else if (*optarg == 'd') timestamp_fmt = DDATE; else errcnt++; } else { errcnt++; } break; case 'h': /* help */ opts->dohelp = 1; break; case '?': default: errcnt++; break; } switch (argc - optind) { case 0: break; case 2: opts->nsamples = strtol(argv[optind + 1], &endp, 10); if (*endp != '\0') { (void) fprintf(stderr, gettext("%s: invalid argument \"%s\"\n"), opts->pgmname, argv[optind + 1]); errcnt++; break; } /*FALLTHROUGH*/ case 1: opts->mseconds = (uint_t)(strtod(argv[optind], &endp) * 1000.0); if (*endp != '\0') { (void) fprintf(stderr, gettext("%s: invalid argument \"%s\"\n"), opts->pgmname, argv[optind]); errcnt++; } break; default: errcnt++; break; } if (opts->nsamples == 0 || opts->mseconds == 0) errcnt++; if (errcnt != 0 || opts->dohelp || (opts->nsets = cpc_setgrp_numsets(opts->master)) == 0) { (void) fprintf(opts->dohelp ? stdout : stderr, gettext( "Usage:\n\t%s -c spec [-c spec]... [-p period] [-T u|d]\n" "\t\t[-sntD] [interval [count]]\n\n" "\t-c spec\t specify processor events to be monitored\n" "\t-n\t suppress titles\n" "\t-p period cycle through event list periodically\n" "\t-s\t run user soaker thread for system-only events\n" "\t-t\t include %s register\n" "\t-T d|u\t Display a timestamp in date (d) or unix " "time_t (u)\n" "\t-D\t enable debug mode\n" "\t-h\t print extended usage information\n\n" "\tUse cputrack(1) to monitor per-process statistics.\n"), opts->pgmname, CPC_TICKREG_NAME); if (opts->dohelp) { (void) putchar('\n'); (void) capabilities(cpc, stdout); exit(0); } exit(2); } /* * If the user requested periodic behavior, calculate the rest time * between cycles. */ if (opts->doperiod) { opts->mseconds_rest = (uint_t)((period * 1000.0) - (opts->mseconds * opts->nsets)); if ((int)opts->mseconds_rest < 0) opts->mseconds_rest = 0; if (opts->nsamples != UINT_MAX) opts->nsamples *= opts->nsets; } cpc_setgrp_reset(opts->master); (void) setvbuf(stdout, NULL, _IOLBF, 0); /* * By design, cpustat (regrettably) has multiple threads racing in * write(2) to generate output. As there are no guarantees made with * respect to the atomicity of concurrent writes on non-O_APPEND file * descriptors, we must set O_APPEND on stdout to assure that no output * is lost. If cpustat is rearchitected such that only one thread is * generating output (which would also assure that the output is always * in a consistent order), this code should be removed. */ if (fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_APPEND) == -1) { (void) fprintf(stderr, gettext("%s: cannot set output to be " "append-only - %s\n"), opts->pgmname, strerror(errno)); return (1); } /* * If no system-mode only sets were created, no soaker threads will be * needed. */ if (opts->dosoaker == 1 && cpc_setgrp_has_sysonly(opts->master) == 0) opts->dosoaker = 0; ret = cpustat(); (void) cpc_close(cpc); return (ret); } static void print_title(cpc_setgrp_t *sgrp) { (void) printf("%7s %3s %5s ", "time", "cpu", "event"); if (opts->dotick) (void) printf("%9s ", CPC_TICKREG_NAME); (void) printf("%s\n", cpc_setgrp_gethdr(sgrp)); } static void print_sample(processorid_t cpuid, cpc_buf_t *buf, int nreq, const char *setname, int sibling) { char line[1024]; int ccnt; int i; uint64_t val; uint64_t tick; hrtime_t hrtime; hrtime = cpc_buf_hrtime(cpc, buf); tick = cpc_buf_tick(cpc, buf); ccnt = snprintf(line, sizeof (line), "%7.3f %3d %5s ", mstimestamp(hrtime), (int)cpuid, "tick"); if (opts->dotick) ccnt += snprintf(line + ccnt, sizeof (line) - ccnt, "%9" PRId64 " ", tick); for (i = 0; i < nreq; i++) { (void) cpc_buf_get(cpc, buf, i, &val); ccnt += snprintf(line + ccnt, sizeof (line) - ccnt, "%9" PRId64 " ", val); } if (opts->nsets > 1) ccnt += snprintf(line + ccnt, sizeof (line) - ccnt, " # %s\n", setname); else ccnt += snprintf(line + ccnt, sizeof (line) - ccnt, "\n"); if (sibling) { /* * This sample is being printed for a "sibling" CPU -- that is, * a CPU which does not have its own CPC set bound. It is being * measured via a set bound to another CPU sharing its physical * processor. */ int designee = chip_designees[gstate[cpuid].chip_id]; char *p; if ((p = strrchr(line, '#')) == NULL) p = strrchr(line, '\n'); if (p != NULL) { *p = '\0'; ccnt = strlen(line); ccnt += snprintf(line + ccnt, sizeof (line) - ccnt, "# counter shared with CPU %d\n", designee); } } if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); if (ccnt > sizeof (line)) ccnt = sizeof (line); if (ccnt > 0) (void) write(1, line, ccnt); /* * If this CPU is the chip designee for any other CPUs, print a line for * them here. */ if (smt && (sibling == 0)) { for (i = 0; i < ncpus; i++) { if ((i != cpuid) && (gstate[i].cpuid != -1) && (chip_designees[gstate[i].chip_id] == cpuid)) print_sample(i, buf, nreq, setname, 1); } } } static void print_total(int ncpus, cpc_buf_t *buf, int nreq, const char *setname) { int i; uint64_t val; (void) printf("%7.3f %3d %5s ", mstimestamp(cpc_buf_hrtime(cpc, buf)), ncpus, "total"); if (opts->dotick) (void) printf("%9" PRId64 " ", cpc_buf_tick(cpc, buf)); for (i = 0; i < nreq; i++) { (void) cpc_buf_get(cpc, buf, i, &val); (void) printf("%9" PRId64 " ", val); } if (opts->nsets > 1) (void) printf(" # %s", setname); (void) fputc('\n', stdout); } #define NSECS_PER_MSEC 1000000ll #define NSECS_PER_SEC 1000000000ll static void * gtick(void *arg) { struct tstate *state = arg; char *errstr; uint_t nsamples; uint_t sample_cnt = 1; hrtime_t ht, htdelta, restdelta; cpc_setgrp_t *sgrp = state->sgrp; cpc_set_t *this = cpc_setgrp_getset(sgrp); const char *name = cpc_setgrp_getname(sgrp); cpc_buf_t **data1, **data2, **scratch; cpc_buf_t *tmp; int nreqs; thread_t tid; htdelta = NSECS_PER_MSEC * opts->mseconds; restdelta = NSECS_PER_MSEC * opts->mseconds_rest; ht = gethrtime(); /* * If this CPU is SMT, we run one gtick() thread per _physical_ CPU, * instead of per cpu_t. The following check returns if it detects that * this cpu_t has not been designated to do the counting for this * physical CPU. */ if (smt && chip_designees[state->chip_id] != state->cpuid) return (NULL); /* * If we need to run a soaker thread on this CPU, start it here. */ if (opts->dosoaker) { if (cond_init(&state->soak_cv, USYNC_THREAD, NULL) != 0) goto bad; if (mutex_init(&state->soak_lock, USYNC_THREAD, NULL) != 0) goto bad; (void) mutex_lock(&state->soak_lock); state->soak_state = SOAK_PAUSE; if (thr_create(NULL, 0, soaker, state, 0, &tid) != 0) goto bad; while (state->soak_state == SOAK_PAUSE) (void) cond_wait(&state->soak_cv, &state->soak_lock); (void) mutex_unlock(&state->soak_lock); /* * If the soaker needs to pause for the first set, stop it now. */ if (cpc_setgrp_sysonly(sgrp) == 0) { (void) mutex_lock(&state->soak_lock); state->soak_state = SOAK_PAUSE; (void) mutex_unlock(&state->soak_lock); } } if (cpc_bind_cpu(cpc, state->cpuid, this, 0) == -1) goto bad; for (nsamples = opts->nsamples; nsamples; nsamples--, sample_cnt++) { hrtime_t htnow; struct timespec ts; nreqs = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); ht += htdelta; htnow = gethrtime(); if (ht <= htnow) continue; ts.tv_sec = (time_t)((ht - htnow) / NSECS_PER_SEC); ts.tv_nsec = (suseconds_t)((ht - htnow) % NSECS_PER_SEC); (void) nanosleep(&ts, NULL); if (opts->nsets == 1) { /* * If we're dealing with one set, buffer usage is: * * data1 = most recent data snapshot * data2 = previous data snapshot * scratch = used for diffing data1 and data2 * * Save the snapshot from the previous sample in data2 * before putting the current sample in data1. */ tmp = *data1; *data1 = *data2; *data2 = tmp; if (cpc_set_sample(cpc, this, *data1) != 0) goto bad; cpc_buf_sub(cpc, *scratch, *data1, *data2); print_sample(state->cpuid, *scratch, nreqs, name, 0); } else { /* * More than one set is in use (multiple -c options * given). Buffer usage in this case is: * * data1 = total counts for this set since program began * data2 = unused * scratch = most recent data snapshot */ name = cpc_setgrp_getname(sgrp); nreqs = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); if (cpc_set_sample(cpc, this, *scratch) != 0) goto bad; cpc_buf_add(cpc, *data1, *data1, *scratch); if (cpc_unbind(cpc, this) != 0) (void) fprintf(stderr, gettext("%s: error " "unbinding on cpu %d - %s\n"), opts->pgmname, state->cpuid, strerror(errno)); this = cpc_setgrp_nextset(sgrp); print_sample(state->cpuid, *scratch, nreqs, name, 0); /* * If periodic behavior was requested, rest here. */ if (opts->doperiod && opts->mseconds_rest > 0 && (sample_cnt % opts->nsets) == 0) { /* * Stop the soaker while the tool rests. */ if (opts->dosoaker) { (void) mutex_lock(&state->soak_lock); if (state->soak_state == SOAK_RUN) state->soak_state = SOAK_PAUSE; (void) mutex_unlock(&state->soak_lock); } htnow = gethrtime(); ht += restdelta; ts.tv_sec = (time_t)((ht - htnow) / NSECS_PER_SEC); ts.tv_nsec = (suseconds_t)((ht - htnow) % NSECS_PER_SEC); (void) nanosleep(&ts, NULL); } /* * Start or stop the soaker if needed. */ if (opts->dosoaker) { (void) mutex_lock(&state->soak_lock); if (cpc_setgrp_sysonly(sgrp) && state->soak_state == SOAK_PAUSE) { /* * Soaker is paused but the next set is * sysonly: start the soaker. */ state->soak_state = SOAK_RUN; (void) cond_signal(&state->soak_cv); } else if (cpc_setgrp_sysonly(sgrp) == 0 && state->soak_state == SOAK_RUN) /* * Soaker is running but the next set * counts user events: stop the soaker. */ state->soak_state = SOAK_PAUSE; (void) mutex_unlock(&state->soak_lock); } if (cpc_bind_cpu(cpc, state->cpuid, this, 0) != 0) goto bad; } } if (cpc_unbind(cpc, this) != 0) (void) fprintf(stderr, gettext("%s: error unbinding on" " cpu %d - %s\n"), opts->pgmname, state->cpuid, strerror(errno)); /* * We're done, so stop the soaker if needed. */ if (opts->dosoaker) { (void) mutex_lock(&state->soak_lock); if (state->soak_state == SOAK_RUN) state->soak_state = SOAK_PAUSE; (void) mutex_unlock(&state->soak_lock); } return (NULL); bad: state->status = 3; errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: cpu%d - %s\n"), opts->pgmname, state->cpuid, errstr); return (NULL); } static int cpustat(void) { cpc_setgrp_t *accum; cpc_set_t *start; int c, i, retval; int lwps = 0; psetid_t mypset, cpupset; char *errstr; cpc_buf_t **data1, **data2, **scratch; int nreqs; kstat_ctl_t *kc; ncpus = (int)sysconf(_SC_NPROCESSORS_CONF); if ((gstate = calloc(ncpus, sizeof (*gstate))) == NULL) { (void) fprintf(stderr, gettext( "%s: out of heap\n"), opts->pgmname); return (1); } max_chip_id = sysconf(_SC_CPUID_MAX); if ((chip_designees = malloc(max_chip_id * sizeof (int))) == NULL) { (void) fprintf(stderr, gettext( "%s: out of heap\n"), opts->pgmname); return (1); } for (i = 0; i < max_chip_id; i++) chip_designees[i] = -1; if (smt) { if ((kc = kstat_open()) == NULL) { (void) fprintf(stderr, gettext( "%s: kstat_open() failed: %s\n"), opts->pgmname, strerror(errno)); return (1); } } if (opts->dosoaker) if (priocntl(0, 0, PC_GETCID, &fxinfo) == -1) { (void) fprintf(stderr, gettext( "%s: couldn't get FX scheduler class: %s\n"), opts->pgmname, strerror(errno)); return (1); } /* * Only include processors that are participating in the system */ for (c = 0, i = 0; i < ncpus; c++) { switch (p_online(c, P_STATUS)) { case P_ONLINE: case P_NOINTR: if (smt) { gstate[i].chip_id = get_chipid(kc, c); if (gstate[i].chip_id != -1 && chip_designees[gstate[i].chip_id] == -1) chip_designees[gstate[i].chip_id] = c; } gstate[i++].cpuid = c; break; case P_OFFLINE: case P_POWEROFF: case P_FAULTED: case P_SPARE: case P_DISABLED: gstate[i++].cpuid = -1; break; default: gstate[i++].cpuid = -1; (void) fprintf(stderr, gettext("%s: cpu%d in unknown state\n"), opts->pgmname, c); break; case -1: break; } } /* * Examine the processor sets; if we're in one, only attempt * to report on the set we're in. */ if (pset_bind(PS_QUERY, P_PID, P_MYID, &mypset) == -1) { errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: pset_bind - %s\n"), opts->pgmname, errstr); } else { for (i = 0; i < ncpus; i++) { struct tstate *this = &gstate[i]; if (this->cpuid == -1) continue; if (pset_assign(PS_QUERY, this->cpuid, &cpupset) == -1) { errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: pset_assign - %s\n"), opts->pgmname, errstr); continue; } if (mypset != cpupset) this->cpuid = -1; } } if (opts->dotitle) print_title(opts->master); zerotime(); for (i = 0; i < ncpus; i++) { struct tstate *this = &gstate[i]; if (this->cpuid == -1) continue; this->sgrp = cpc_setgrp_clone(opts->master); if (this->sgrp == NULL) { this->cpuid = -1; continue; } if (thr_create(NULL, 0, gtick, this, THR_BOUND|THR_NEW_LWP, &this->tid) == 0) lwps++; else { (void) fprintf(stderr, gettext("%s: cannot create thread for cpu%d\n"), opts->pgmname, this->cpuid); this->status = 4; } } if (lwps != 0) for (i = 0; i < ncpus; i++) (void) thr_join(gstate[i].tid, NULL, NULL); if ((accum = cpc_setgrp_clone(opts->master)) == NULL) { (void) fprintf(stderr, gettext("%s: out of heap\n"), opts->pgmname); return (1); } retval = 0; for (i = 0; i < ncpus; i++) { struct tstate *this = &gstate[i]; if (this->cpuid == -1) continue; cpc_setgrp_accum(accum, this->sgrp); cpc_setgrp_free(this->sgrp); this->sgrp = NULL; if (this->status != 0) retval = 1; } cpc_setgrp_reset(accum); start = cpc_setgrp_getset(accum); do { nreqs = cpc_setgrp_getbufs(accum, &data1, &data2, &scratch); print_total(lwps, *data1, nreqs, cpc_setgrp_getname(accum)); } while (cpc_setgrp_nextset(accum) != start); cpc_setgrp_free(accum); accum = NULL; free(gstate); return (retval); } static int get_chipid(kstat_ctl_t *kc, processorid_t cpuid) { kstat_t *ksp; kstat_named_t *k; if ((ksp = kstat_lookup(kc, "cpu_info", cpuid, NULL)) == NULL) return (-1); if (kstat_read(kc, ksp, NULL) == -1) { (void) fprintf(stderr, gettext("%s: kstat_read() failed for cpu %d: %s\n"), opts->pgmname, cpuid, strerror(errno)); return (-1); } if ((k = (kstat_named_t *)kstat_data_lookup(ksp, "chip_id")) == NULL) { (void) fprintf(stderr, gettext("%s: chip_id not found for cpu %d: %s\n"), opts->pgmname, cpuid, strerror(errno)); return (-1); } return (k->value.i32); } static void * soaker(void *arg) { struct tstate *state = arg; pcparms_t pcparms; fxparms_t *fx = (fxparms_t *)pcparms.pc_clparms; if (processor_bind(P_LWPID, P_MYID, state->cpuid, NULL) != 0) (void) fprintf(stderr, gettext("%s: couldn't bind soaker " "thread to cpu%d: %s\n"), opts->pgmname, state->cpuid, strerror(errno)); /* * Put the soaker thread in the fixed priority (FX) class so it runs * at the lowest possible global priority. */ pcparms.pc_cid = fxinfo.pc_cid; fx->fx_upri = 0; fx->fx_uprilim = 0; fx->fx_tqsecs = fx->fx_tqnsecs = FX_TQDEF; if (priocntl(P_LWPID, P_MYID, PC_SETPARMS, &pcparms) != 0) (void) fprintf(stderr, gettext("%s: couldn't put soaker " "thread in FX sched class: %s\n"), opts->pgmname, strerror(errno)); /* * Let the parent thread know we're ready to roll. */ (void) mutex_lock(&state->soak_lock); state->soak_state = SOAK_RUN; (void) cond_signal(&state->soak_cv); (void) mutex_unlock(&state->soak_lock); for (;;) { spin: (void) mutex_lock(&state->soak_lock); if (state->soak_state == SOAK_RUN) { (void) mutex_unlock(&state->soak_lock); goto spin; } while (state->soak_state == SOAK_PAUSE) (void) cond_wait(&state->soak_cv, &state->soak_lock); (void) mutex_unlock(&state->soak_lock); } /*NOTREACHED*/ return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpucmds.h" static struct options { int debug; int verbose; int dotitle; int dohelp; int dotick; int cpuver; char *pgmname; uint_t mseconds; uint_t nsamples; uint_t nsets; cpc_setgrp_t *master; int followfork; int followexec; pid_t pid; FILE *log; } __options; static const struct options *opts = (const struct options *)&__options; static cpc_t *cpc; /* * How many signals caught from terminal * We bail out as soon as possible when interrupt is set */ static int interrupt = 0; /*ARGSUSED*/ static void cputrack_errfn(const char *fn, int subcode, const char *fmt, va_list ap) { (void) fprintf(stderr, "%s: ", opts->pgmname); if (opts->debug) (void) fprintf(stderr, "%s: ", fn); (void) vfprintf(stderr, fmt, ap); } static void cputrack_pctx_errfn(const char *fn, const char *fmt, va_list ap) { cputrack_errfn(fn, -1, fmt, ap); } static int cputrack(int argc, char *argv[], int optind); static void intr(int); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif int main(int argc, char *argv[]) { struct options *opts = &__options; int c, errcnt = 0; int nsamples; cpc_setgrp_t *sgrp; char *errstr; int ret; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if ((opts->pgmname = strrchr(argv[0], '/')) == NULL) opts->pgmname = argv[0]; else opts->pgmname++; if ((cpc = cpc_open(CPC_VER_CURRENT)) == NULL) { errstr = strerror(errno); (void) fprintf(stderr, gettext("%s: cannot access performance " "counter library - %s\n"), opts->pgmname, errstr); return (1); } (void) cpc_seterrhndlr(cpc, cputrack_errfn); strtoset_errfn = cputrack_errfn; /* * Establish (non-zero) defaults */ opts->mseconds = 1000; opts->dotitle = 1; opts->log = stdout; if ((opts->master = cpc_setgrp_new(cpc, 0)) == NULL) { (void) fprintf(stderr, gettext("%s: no memory available\n"), opts->pgmname); exit(1); } while ((c = getopt(argc, argv, "T:N:Defhntvo:r:c:p:")) != EOF) switch (c) { case 'T': /* sample time, seconds */ opts->mseconds = (uint_t)(atof(optarg) * 1000.0); break; case 'N': /* number of samples */ nsamples = atoi(optarg); if (nsamples < 0) errcnt++; else opts->nsamples = (uint_t)nsamples; break; case 'D': /* enable debugging */ opts->debug++; break; case 'f': /* follow fork */ opts->followfork++; break; case 'e': /* follow exec */ opts->followexec++; break; case 'n': /* no titles */ opts->dotitle = 0; break; case 't': /* print %tick */ opts->dotick = 1; break; case 'v': opts->verbose = 1; /* more chatty */ break; case 'o': if (optarg == NULL) { errcnt++; break; } if ((opts->log = fopen(optarg, "w")) == NULL) { (void) fprintf(stderr, gettext( "%s: cannot open '%s' for writing\n"), opts->pgmname, optarg); return (1); } break; case 'c': /* specify statistics */ if ((sgrp = cpc_setgrp_newset(opts->master, optarg, &errcnt)) != NULL) opts->master = sgrp; break; case 'p': /* grab given pid */ if ((opts->pid = atoi(optarg)) <= 0) errcnt++; break; case 'h': opts->dohelp = 1; break; case '?': default: errcnt++; break; } if (opts->nsamples == 0) opts->nsamples = UINT_MAX; if (errcnt != 0 || opts->dohelp || (argc == optind && opts->pid == 0) || (argc > optind && opts->pid != 0) || (opts->nsets = cpc_setgrp_numsets(opts->master)) == 0) { (void) fprintf(opts->dohelp ? stdout : stderr, gettext( "Usage:\n\t%s [-T secs] [-N count] [-Defhnv] [-o file]\n" "\t\t-c events [command [args] | -p pid]\n\n" "\t-T secs\t seconds between samples, default 1\n" "\t-N count number of samples, default unlimited\n" "\t-D\t enable debug mode\n" "\t-e\t follow exec(2), and execve(2)\n" "\t-f\t follow fork(2), fork1(2), and vfork(2)\n" "\t-h\t print extended usage information\n" "\t-n\t suppress titles\n" "\t-t\t include virtualized %s register\n" "\t-v\t verbose mode\n" "\t-o file\t write cpu statistics to this file\n" "\t-c events specify processor events to be monitored\n" "\t-p pid\t pid of existing process to capture\n\n" "\tUse cpustat(8) to monitor system-wide statistics.\n"), opts->pgmname, CPC_TICKREG_NAME); if (opts->dohelp) { (void) putchar('\n'); (void) capabilities(cpc, stdout); exit(0); } exit(2); } /* * Catch signals from terminal, so they can be handled asynchronously * when we're ready instead of when we're not (;-) */ if (sigset(SIGHUP, SIG_IGN) == SIG_DFL) (void) sigset(SIGHUP, intr); if (sigset(SIGINT, SIG_IGN) == SIG_DFL) (void) sigset(SIGINT, intr); if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL) (void) sigset(SIGQUIT, intr); (void) sigset(SIGPIPE, intr); (void) sigset(SIGTERM, intr); cpc_setgrp_reset(opts->master); (void) setvbuf(opts->log, NULL, _IOLBF, 0); ret = cputrack(argc, argv, optind); (void) cpc_close(cpc); return (ret); } static void print_title(cpc_setgrp_t *sgrp) { (void) fprintf(opts->log, "%7s ", "time"); if (opts->followfork) (void) fprintf(opts->log, "%6s ", "pid"); (void) fprintf(opts->log, "%3s %10s ", "lwp", "event"); if (opts->dotick) (void) fprintf(opts->log, "%9s ", CPC_TICKREG_NAME); (void) fprintf(opts->log, "%s\n", cpc_setgrp_gethdr(sgrp)); (void) fflush(opts->log); } static void print_exec(float now, pid_t pid, char *name) { if (name == NULL) name = "(unknown)"; (void) fprintf(opts->log, "%7.3f ", now); if (opts->followfork) (void) fprintf(opts->log, "%6d ", (int)pid); (void) fprintf(opts->log, "%3d %10s ", 1, "exec"); if (opts->dotick) (void) fprintf(opts->log, "%9s ", ""); (void) fprintf(opts->log, "%9s %9s # '%s'\n", "", "", name); (void) fflush(opts->log); } static void print_fork(float now, pid_t newpid, id_t lwpid, pid_t oldpid) { (void) fprintf(opts->log, "%7.3f ", now); if (opts->followfork) (void) fprintf(opts->log, "%6d ", (int)oldpid); (void) fprintf(opts->log, "%3d %10s ", (int)lwpid, "fork"); if (opts->dotick) (void) fprintf(opts->log, "%9s ", ""); (void) fprintf(opts->log, "%9s %9s # %d\n", "", "", (int)newpid); (void) fflush(opts->log); } static void print_sample(pid_t pid, id_t lwpid, char *pevent, cpc_buf_t *buf, int nreq, const char *evname) { uint64_t val; int i; (void) fprintf(opts->log, "%7.3f ", mstimestamp(cpc_buf_hrtime(cpc, buf))); if (opts->followfork) (void) fprintf(opts->log, "%6d ", (int)pid); (void) fprintf(opts->log, "%3d %10s ", (int)lwpid, pevent); if (opts->dotick) (void) fprintf(opts->log, "%9" PRId64 " ", cpc_buf_tick(cpc, buf)); for (i = 0; i < nreq; i++) { (void) cpc_buf_get(cpc, buf, i, &val); (void) fprintf(opts->log, "%9" PRId64 " ", val); } if (opts->nsets > 1) (void) fprintf(opts->log, " # %s\n", evname); else (void) fputc('\n', opts->log); } struct pstate { cpc_setgrp_t *accum; cpc_setgrp_t **sgrps; int maxlwpid; }; static int pinit_lwp(pctx_t *pctx, pid_t pid, id_t lwpid, void *arg) { struct pstate *state = arg; cpc_setgrp_t *sgrp; cpc_set_t *set; cpc_buf_t **data1, **data2, **scratch; char *errstr; int nreq; if (interrupt) return (0); if (state->maxlwpid < lwpid) { state->sgrps = realloc(state->sgrps, lwpid * sizeof (state->sgrps)); if (state->sgrps == NULL) { (void) fprintf(stderr, gettext( "%6d: init_lwp: out of memory\n"), (int)pid); return (-1); } while (state->maxlwpid < lwpid) { state->sgrps[state->maxlwpid] = NULL; state->maxlwpid++; } } if ((sgrp = state->sgrps[lwpid-1]) == NULL) { if ((sgrp = cpc_setgrp_clone(opts->master)) == NULL) { (void) fprintf(stderr, gettext( "%6d: init_lwp: out of memory\n"), (int)pid); return (-1); } state->sgrps[lwpid-1] = sgrp; set = cpc_setgrp_getset(sgrp); } else { cpc_setgrp_reset(sgrp); set = cpc_setgrp_getset(sgrp); } nreq = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); if (cpc_bind_pctx(cpc, pctx, lwpid, set, 0) != 0 || cpc_set_sample(cpc, set, *data2) != 0) { errstr = strerror(errno); if (errno == EAGAIN) { (void) cpc_unbind(cpc, set); } (void) fprintf(stderr, gettext( "%6d: init_lwp: can't bind perf counters " "to lwp%d - %s\n"), (int)pid, (int)lwpid, errstr); return (-1); } if (opts->verbose) print_sample(pid, lwpid, "init_lwp", *data2, nreq, cpc_setgrp_getname(sgrp)); return (0); } /*ARGSUSED*/ static int pfini_lwp(pctx_t *pctx, pid_t pid, id_t lwpid, void *arg) { struct pstate *state = arg; cpc_setgrp_t *sgrp = state->sgrps[lwpid-1]; cpc_set_t *set; char *errstr; cpc_buf_t **data1, **data2, **scratch; int nreq; if (interrupt) return (0); set = cpc_setgrp_getset(sgrp); nreq = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); if (cpc_set_sample(cpc, set, *scratch) == 0) { if (opts->nsets == 1) { /* * When we only have one set of counts, the sample * gives us the accumulated count. */ *data1 = *scratch; } else { /* * When we have more than one set of counts, the * sample gives us the count for the latest sample * period. *data1 contains the accumulated count but * does not include the count for the latest sample * period for this set of counters. */ cpc_buf_add(cpc, *data1, *data1, *scratch); } if (opts->verbose) print_sample(pid, lwpid, "fini_lwp", *data1, nreq, cpc_setgrp_getname(sgrp)); cpc_setgrp_accum(state->accum, sgrp); if (cpc_unbind(cpc, set) == 0) return (0); } switch (errno) { case EAGAIN: (void) fprintf(stderr, gettext("%6d: fini_lwp: " "lwp%d: perf counter contents invalidated\n"), (int)pid, (int)lwpid); break; default: errstr = strerror(errno); (void) fprintf(stderr, gettext("%6d: fini_lwp: " "lwp%d: can't access perf counters - %s\n"), (int)pid, (int)lwpid, errstr); break; } return (-1); } /*ARGSUSED*/ static int plwp_create(pctx_t *pctx, pid_t pid, id_t lwpid, void *arg) { cpc_setgrp_t *sgrp = opts->master; cpc_buf_t **data1, **data2, **scratch; int nreq; if (interrupt) return (0); nreq = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); print_sample(pid, lwpid, "lwp_create", *data1, nreq, cpc_setgrp_getname(sgrp)); return (0); } /*ARGSUSED*/ static int plwp_exit(pctx_t *pctx, pid_t pid, id_t lwpid, void *arg) { struct pstate *state = arg; cpc_setgrp_t *sgrp = state->sgrps[lwpid-1]; cpc_set_t *start; int nreq; cpc_buf_t **data1, **data2, **scratch; if (interrupt) return (0); start = cpc_setgrp_getset(sgrp); do { nreq = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); if (cpc_buf_hrtime(cpc, *data1) == 0) continue; print_sample(pid, lwpid, "lwp_exit", *data1, nreq, cpc_setgrp_getname(sgrp)); } while (cpc_setgrp_nextset(sgrp) != start); return (0); } /*ARGSUSED*/ static int pexec(pctx_t *pctx, pid_t pid, id_t lwpid, char *name, void *arg) { struct pstate *state = arg; float now = 0.0; cpc_set_t *start; int nreq; cpc_buf_t **data1, **data2, **scratch; hrtime_t hrt; if (interrupt) return (0); /* * Print the accumulated results from the previous program image */ cpc_setgrp_reset(state->accum); start = cpc_setgrp_getset(state->accum); do { nreq = cpc_setgrp_getbufs(state->accum, &data1, &data2, &scratch); hrt = cpc_buf_hrtime(cpc, *data1); if (hrt == 0) continue; print_sample(pid, lwpid, "exec", *data1, nreq, cpc_setgrp_getname(state->accum)); if (now < mstimestamp(hrt)) now = mstimestamp(hrt); } while (cpc_setgrp_nextset(state->accum) != start); print_exec(now, pid, name); if (state->accum != NULL) { cpc_setgrp_free(state->accum); state->accum = NULL; } if (opts->followexec) { state->accum = cpc_setgrp_clone(opts->master); return (0); } return (-1); } /*ARGSUSED*/ static void pexit(pctx_t *pctx, pid_t pid, id_t lwpid, int status, void *arg) { struct pstate *state = arg; cpc_set_t *start; int nreq; cpc_buf_t **data1, **data2, **scratch; if (interrupt) return; cpc_setgrp_reset(state->accum); start = cpc_setgrp_getset(state->accum); do { nreq = cpc_setgrp_getbufs(state->accum, &data1, &data2, &scratch); if (cpc_buf_hrtime(cpc, *data1) == 0) continue; print_sample(pid, lwpid, "exit", *data1, nreq, cpc_setgrp_getname(state->accum)); } while (cpc_setgrp_nextset(state->accum) != start); cpc_setgrp_free(state->accum); state->accum = NULL; for (lwpid = 1; lwpid < state->maxlwpid; lwpid++) if (state->sgrps[lwpid-1] != NULL) { cpc_setgrp_free(state->sgrps[lwpid-1]); state->sgrps[lwpid-1] = NULL; } free(state->sgrps); state->sgrps = NULL; } static int ptick(pctx_t *pctx, pid_t pid, id_t lwpid, void *arg) { struct pstate *state = arg; cpc_setgrp_t *sgrp = state->sgrps[lwpid-1]; cpc_set_t *this = cpc_setgrp_getset(sgrp); const char *name = cpc_setgrp_getname(sgrp); cpc_buf_t **data1, **data2, **scratch, *tmp; char *errstr; int nreqs; if (interrupt) return (0); nreqs = cpc_setgrp_getbufs(sgrp, &data1, &data2, &scratch); if (opts->nsets == 1) { /* * If we're dealing with one set, buffer usage is: * * data1 = most recent data snapshot * data2 = previous data snapshot * scratch = used for diffing data1 and data2 * * Save the snapshot from the previous sample in data2 * before putting the current sample in data1. */ tmp = *data1; *data1 = *data2; *data2 = tmp; if (cpc_set_sample(cpc, this, *data1) != 0) goto broken; cpc_buf_sub(cpc, *scratch, *data1, *data2); } else { cpc_set_t *next = cpc_setgrp_nextset(sgrp); /* * If there is more than set in use, we will need to * unbind and re-bind on each go-around because each * time a counter is bound, it is preset to 0 (as it was * specified when the requests were added to the set). * * Buffer usage in this case is: * * data1 = total counts for this set since program began * data2 = unused * scratch = most recent data snapshot */ if (cpc_set_sample(cpc, this, *scratch) != 0) goto broken; cpc_buf_add(cpc, *data1, *data1, *scratch); /* * No need to unbind the previous set, as binding another set * automatically unbinds the most recently bound set. */ if (cpc_bind_pctx(cpc, pctx, lwpid, next, 0) != 0) goto broken; } print_sample(pid, lwpid, "tick", *scratch, nreqs, name); return (0); broken: switch (errno) { case EAGAIN: (void) fprintf(stderr, gettext( "%6d: tick: lwp%d: perf counter contents invalidated\n"), (int)pid, (int)lwpid); break; default: errstr = strerror(errno); (void) fprintf(stderr, gettext( "%6d: tick: lwp%d: can't access perf counter - %s\n"), (int)pid, (int)lwpid, errstr); break; } (void) cpc_unbind(cpc, this); return (-1); } /* * The system has just created a new address space that has a new pid. * We're running in a child of the controlling process, with a new * pctx handle already opened on the child of the original controlled process. */ static void pfork(pctx_t *pctx, pid_t oldpid, pid_t pid, id_t lwpid, void *arg) { struct pstate *state = arg; print_fork(mstimestamp(0), pid, lwpid, oldpid); if (!opts->followfork) return; if (pctx_set_events(pctx, PCTX_SYSC_EXEC_EVENT, pexec, PCTX_SYSC_FORK_EVENT, pfork, PCTX_SYSC_EXIT_EVENT, pexit, PCTX_SYSC_LWP_CREATE_EVENT, plwp_create, PCTX_INIT_LWP_EVENT, pinit_lwp, PCTX_FINI_LWP_EVENT, pfini_lwp, PCTX_SYSC_LWP_EXIT_EVENT, plwp_exit, PCTX_NULL_EVENT) == 0) { state->accum = cpc_setgrp_clone(opts->master); (void) pctx_run(pctx, opts->mseconds, opts->nsamples, ptick); if (state->accum) { free(state->accum); state->accum = NULL; } } } /* * Translate the incoming options into actions, and get the * tool and the process to control running. */ static int cputrack(int argc, char *argv[], int optind) { struct pstate __state, *state = &__state; pctx_t *pctx; int err; bzero(state, sizeof (*state)); if (opts->pid == 0) { if (argc <= optind) { (void) fprintf(stderr, "%s: %s\n", opts->pgmname, gettext("no program to start")); return (1); } pctx = pctx_create(argv[optind], &argv[optind], state, 1, cputrack_pctx_errfn); if (pctx == NULL) { (void) fprintf(stderr, "%s: %s '%s'\n", opts->pgmname, gettext("failed to start program"), argv[optind]); return (1); } } else { pctx = pctx_capture(opts->pid, state, 1, cputrack_pctx_errfn); if (pctx == NULL) { (void) fprintf(stderr, "%s: %s %d\n", opts->pgmname, gettext("failed to capture pid"), (int)opts->pid); return (1); } } err = pctx_set_events(pctx, PCTX_SYSC_EXEC_EVENT, pexec, PCTX_SYSC_FORK_EVENT, pfork, PCTX_SYSC_EXIT_EVENT, pexit, PCTX_SYSC_LWP_CREATE_EVENT, plwp_create, PCTX_INIT_LWP_EVENT, pinit_lwp, PCTX_FINI_LWP_EVENT, pfini_lwp, PCTX_SYSC_LWP_EXIT_EVENT, plwp_exit, PCTX_NULL_EVENT); if (err != 0) { (void) fprintf(stderr, "%s: %s\n", opts->pgmname, gettext("can't bind process context ops to process")); } else { if (opts->dotitle) print_title(opts->master); state->accum = cpc_setgrp_clone(opts->master); zerotime(); err = pctx_run(pctx, opts->mseconds, opts->nsamples, ptick); if (state->accum) { cpc_setgrp_free(state->accum); state->accum = NULL; } } return (err != 0 ? 1 : 0); } /*ARGSUSED*/ static void intr(int sig) { interrupt++; if (cpc != NULL) cpc_terminate(cpc); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include "cpucmds.h" #define CHARS_PER_REQ 11 /* space required for printing column headers */ /* * These routines are solely used to manage a list of request sets. */ struct __cpc_setgrp { struct setgrp_elem { cpc_set_t *set; uint8_t sysonly; /* All reqs sys-mode only ? */ int nreqs; int *picnums; /* picnum used per req */ cpc_buf_t *data1; cpc_buf_t *data2; cpc_buf_t *scratch; char *name; char *hdr; } *sets; /* array of events and names */ int nelem; /* size of array */ int current; /* currently bound event in eventset */ int smt; /* Measures physical events on SMT CPU */ int has_sysonly_set; /* Does this group have a system-only set? */ cpc_t *cpc; /* library handle */ }; static void *emalloc(size_t n); cpc_setgrp_t * cpc_setgrp_new(cpc_t *cpc, int smt) { cpc_setgrp_t *sgrp; sgrp = emalloc(sizeof (*sgrp)); sgrp->current = -1; sgrp->cpc = cpc; sgrp->smt = smt; sgrp->has_sysonly_set = 0; return (sgrp); } /* * Walker to count the number of requests in a set, and check if any requests * count user-mode events. */ /*ARGSUSED*/ static void cpc_setgrp_walker(void *arg, int index, const char *event, uint64_t preset, uint_t flags, int nattrs, const cpc_attr_t *attrs) { struct setgrp_elem *se = arg; se->nreqs++; if (flags & CPC_COUNT_USER) se->sysonly = 0; } /* * Walker to discover the picnums used by the requests in a set. */ /*ARGSUSED*/ static void cpc_setgrp_picwalker(void *arg, int index, const char *event, uint64_t preset, uint_t flags, int nattrs, const cpc_attr_t *attrs) { int *picnums = arg; int i; for (i = 0; i < nattrs; i++) { if (strncmp(attrs[i].ca_name, "picnum", 7) == 0) break; } if (i == nattrs) picnums[index] = -1; picnums[index] = (int)attrs[i].ca_val; } cpc_setgrp_t * cpc_setgrp_newset(cpc_setgrp_t *sgrp, const char *spec, int *errcnt) { cpc_set_t *set; struct setgrp_elem *new; char hdr[CHARS_PER_REQ+1]; int i; if ((set = cpc_strtoset(sgrp->cpc, spec, sgrp->smt)) == NULL) { *errcnt += 1; return (NULL); } if ((new = realloc(sgrp->sets, (1 + sgrp->nelem) * sizeof (*new))) == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no re memory available\n")); exit(0); } sgrp->sets = new; sgrp->sets[sgrp->nelem].set = set; /* * Count the number of requests in the set we just made. If any requests * in the set have CPC_COUNT_USER in the flags, the sysonly flag will * be cleared. */ sgrp->sets[sgrp->nelem].nreqs = 0; sgrp->sets[sgrp->nelem].sysonly = 1; cpc_walk_requests(sgrp->cpc, set, &(sgrp->sets[sgrp->nelem]), cpc_setgrp_walker); if (sgrp->sets[sgrp->nelem].sysonly == 1) sgrp->has_sysonly_set = 1; sgrp->sets[sgrp->nelem].picnums = emalloc(sgrp->sets[sgrp->nelem].nreqs * sizeof (int)); sgrp->sets[sgrp->nelem].hdr = emalloc((sgrp->sets[sgrp->nelem].nreqs * CHARS_PER_REQ) + 1); /* * Find out which picnums the requests are using. */ cpc_walk_requests(sgrp->cpc, set, sgrp->sets[sgrp->nelem].picnums, cpc_setgrp_picwalker); /* * Use the picnums we discovered to build a printable header for this * set. */ sgrp->sets[sgrp->nelem].hdr[0] = '\0'; for (i = 0; i < sgrp->sets[sgrp->nelem].nreqs; i++) { (void) snprintf(hdr, CHARS_PER_REQ, "%8s%-2d ", "pic", sgrp->sets[sgrp->nelem].picnums[i]); (void) strncat(sgrp->sets[sgrp->nelem].hdr, hdr, sgrp->sets[sgrp->nelem].nreqs * CHARS_PER_REQ); } sgrp->sets[sgrp->nelem].hdr[strlen(sgrp->sets[sgrp->nelem].hdr)] = '\0'; if ((sgrp->sets[sgrp->nelem].name = strdup(spec)) == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no memory available\n")); exit(0); } if ((sgrp->sets[sgrp->nelem].data1 = cpc_buf_create(sgrp->cpc, set)) == NULL || (sgrp->sets[sgrp->nelem].data2 = cpc_buf_create(sgrp->cpc, set)) == NULL || (sgrp->sets[sgrp->nelem].scratch = cpc_buf_create(sgrp->cpc, set)) == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no memory available\n")); exit(0); } if (sgrp->current < 0) sgrp->current = 0; sgrp->nelem++; return (sgrp); } int cpc_setgrp_getbufs(cpc_setgrp_t *sgrp, cpc_buf_t ***data1, cpc_buf_t ***data2, cpc_buf_t ***scratch) { if ((uint_t)sgrp->current >= sgrp->nelem) return (-1); *data1 = &(sgrp->sets[sgrp->current].data1); *data2 = &(sgrp->sets[sgrp->current].data2); *scratch = &(sgrp->sets[sgrp->current].scratch); return (sgrp->sets[sgrp->current].nreqs); } cpc_setgrp_t * cpc_setgrp_clone(cpc_setgrp_t *old) { int i; cpc_setgrp_t *new; struct setgrp_elem *newa; new = emalloc(sizeof (*new)); newa = emalloc(old->nelem * sizeof (*newa)); new->nelem = old->nelem; new->current = old->current; new->cpc = old->cpc; new->sets = newa; new->smt = old->smt; new->has_sysonly_set = old->has_sysonly_set; for (i = 0; i < old->nelem; i++) { if ((newa[i].set = cpc_strtoset(old->cpc, old->sets[i].name, old->smt)) == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: cpc_strtoset() failed\n")); exit(0); } if ((newa[i].name = strdup(old->sets[i].name)) == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no memory available\n")); exit(0); } newa[i].sysonly = old->sets[i].sysonly; newa[i].nreqs = old->sets[i].nreqs; newa[i].data1 = cpc_buf_create(old->cpc, newa[i].set); newa[i].data2 = cpc_buf_create(old->cpc, newa[i].set); newa[i].scratch = cpc_buf_create(old->cpc, newa[i].set); if (newa[i].data1 == NULL || newa[i].data2 == NULL || newa[i].scratch == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no memory available\n")); exit(0); } cpc_buf_copy(old->cpc, newa[i].data1, old->sets[i].data1); cpc_buf_copy(old->cpc, newa[i].data2, old->sets[i].data2); cpc_buf_copy(old->cpc, newa[i].scratch, old->sets[i].scratch); } return (new); } static void cpc_setgrp_delset(cpc_setgrp_t *sgrp) { int l; if ((uint_t)sgrp->current >= sgrp->nelem) sgrp->current = sgrp->nelem - 1; if (sgrp->current < 0) return; free(sgrp->sets[sgrp->current].name); free(sgrp->sets[sgrp->current].hdr); free(sgrp->sets[sgrp->current].picnums); (void) cpc_buf_destroy(sgrp->cpc, sgrp->sets[sgrp->current].data1); (void) cpc_buf_destroy(sgrp->cpc, sgrp->sets[sgrp->current].data2); (void) cpc_buf_destroy(sgrp->cpc, sgrp->sets[sgrp->current].scratch); for (l = sgrp->current; l < sgrp->nelem - 1; l++) sgrp->sets[l] = sgrp->sets[l + 1]; sgrp->nelem--; } void cpc_setgrp_free(cpc_setgrp_t *sgrp) { if (sgrp->sets) { while (sgrp->nelem) cpc_setgrp_delset(sgrp); free(sgrp->sets); } free(sgrp); } cpc_set_t * cpc_setgrp_getset(cpc_setgrp_t *sgrp) { if ((uint_t)sgrp->current >= sgrp->nelem) return (NULL); return (sgrp->sets[sgrp->current].set); } const char * cpc_setgrp_getname(cpc_setgrp_t *sgrp) { if ((uint_t)sgrp->current >= sgrp->nelem) return (NULL); return (sgrp->sets[sgrp->current].name); } const char * cpc_setgrp_gethdr(cpc_setgrp_t *sgrp) { if ((uint_t)sgrp->current >= sgrp->nelem) return (NULL); return (sgrp->sets[sgrp->current].hdr); } int cpc_setgrp_numsets(cpc_setgrp_t *sgrp) { return (sgrp->nelem); } cpc_set_t * cpc_setgrp_nextset(cpc_setgrp_t *sgrp) { if (sgrp->current < 0) return (NULL); if (++sgrp->current >= sgrp->nelem) sgrp->current = 0; return (cpc_setgrp_getset(sgrp)); } /* * Put the setgrp pointer back to the beginning of the set */ void cpc_setgrp_reset(cpc_setgrp_t *sgrp) { if (sgrp->current > 0) sgrp->current = 0; } /* * Adds the data from the 'data1' buf into the accum setgrp. */ void cpc_setgrp_accum(cpc_setgrp_t *accum, cpc_setgrp_t *sgrp) { int i; cpc_setgrp_reset(accum); cpc_setgrp_reset(sgrp); if (accum->nelem != sgrp->nelem) return; for (i = 0; i < sgrp->nelem; i++) { if (accum->sets[i].nreqs != sgrp->sets[i].nreqs) return; cpc_buf_add(sgrp->cpc, accum->sets[i].data1, accum->sets[i].data1, sgrp->sets[i].data1); } } /* * Returns 1 if all requests in the current set count only system-mode events. */ int cpc_setgrp_sysonly(cpc_setgrp_t *sgrp) { return ((int)sgrp->sets[sgrp->current].sysonly); } /* * Returns 1 if any set in the group is a system-mode-only set. */ int cpc_setgrp_has_sysonly(cpc_setgrp_t *sgrp) { return (sgrp->has_sysonly_set); } /* * If we ever fail to get memory, we print an error message and exit. */ static void * emalloc(size_t n) { /* * Several callers of this routine need zero-filled buffers. */ void *p = calloc(1, n); if (p == NULL) { (void) fprintf(stderr, gettext("cpc_setgrp: no memory available\n")); exit(0); } return (p); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include "libcpc.h" /* * Takes a string and converts it to a cpc_set_t. * * While processing the string using getsubopt(), we will use an array of * requests to hold the data, and a proprietary representation of attributes * which allow us to avoid a realloc()/bcopy() dance every time we come across * a new attribute. * * Not until after the string has been processed in its entirety do we * allocate and specify a request set properly. */ /* * Leave enough room in token strings for picn, nousern, or sysn where n is * picnum. */ #define TOK_SIZE 10 typedef struct __tmp_attr { char *name; uint64_t val; struct __tmp_attr *next; } tmp_attr_t; typedef struct __tok_info { char *name; int picnum; } tok_info_t; typedef struct __request_t { char cr_event[CPC_MAX_EVENT_LEN]; uint_t cr_flags; uint_t cr_nattrs; /* # CPU-specific attrs */ } request_t; static void strtoset_cleanup(void); static void smt_special(int picnum); static void *emalloc(size_t n); /* * Clients of cpc_strtoset may set this to specify an error handler during * string parsing. */ cpc_errhndlr_t *strtoset_errfn = NULL; static request_t *reqs; static int nreqs; static int ncounters; static tmp_attr_t **attrs; static int ntoks; static char **toks; static tok_info_t *tok_info; static int (*(*tok_funcs))(int, char *); static char **attrlist; /* array of ptrs to toks in attrlistp */ static int nattrs; static cpc_t *cpc; static int found; static void strtoset_err(const char *fmt, ...) { va_list ap; if (strtoset_errfn == NULL) return; va_start(ap, fmt); (*strtoset_errfn)("cpc_strtoset", -1, fmt, ap); va_end(ap); } /*ARGSUSED*/ static void event_walker(void *arg, uint_t picno, const char *event) { if (strncmp(arg, event, CPC_MAX_EVENT_LEN) == 0) found = 1; } static int event_valid(int picnum, char *event) { char *end_event; int err; found = 0; cpc_walk_events_pic(cpc, picnum, event, event_walker); if (found) return (1); cpc_walk_generic_events_pic(cpc, picnum, event, event_walker); if (found) return (1); /* * Before assuming this is an invalid event, see if we have been given * a raw event code. * Check the second argument of strtol() to ensure invalid events * beginning with number do not go through. */ err = errno; errno = 0; (void) strtol(event, &end_event, 0); if ((errno == 0) && (*end_event == '\0')) { /* * Success - this is a valid raw code in hex, decimal, or octal. */ errno = err; return (1); } errno = err; return (0); } /* * An unknown token was encountered; check here if it is an implicit event * name. We allow users to omit the "picn=" portion of the event spec, and * assign such events to available pics in order they are returned from * getsubopt(3C). We start our search for an available pic _after_ the highest * picnum to be assigned. This ensures that the event spec can never be out of * order; i.e. if the event string is "eventa,eventb" we must ensure that the * picnum counting eventa is less than the picnum counting eventb. */ static int find_event(char *event) { int i; /* * Event names cannot have '=' in them. If present here, it means we * have encountered an unknown token (foo=bar, for example). */ if (strchr(event, '=') != NULL) return (0); /* * Find the first unavailable pic, after which we must start our search. */ for (i = ncounters - 1; i >= 0; i--) { if (reqs[i].cr_event[0] != '\0') break; } /* * If the last counter has been assigned, we cannot place this event. */ if (i == ncounters - 1) return (0); /* * If none of the counters have been assigned yet, i is -1 and we will * begin our search at 0. Else we begin our search at the counter after * the last one currently assigned. */ i++; for (; i < ncounters; i++) { if (event_valid(i, event) == 0) continue; nreqs++; (void) strncpy(reqs[i].cr_event, event, CPC_MAX_EVENT_LEN); return (1); } return (0); } static int pic(int tok, char *val) { int picnum = tok_info[tok].picnum; /* * Make sure the each pic only appears in the spec once. */ if (reqs[picnum].cr_event[0] != '\0') { strtoset_err(gettext("repeated 'pic%d' token\n"), picnum); return (-1); } if (val == NULL || val[0] == '\0') { strtoset_err(gettext("missing 'pic%d' value\n"), picnum); return (-1); } if (event_valid(picnum, val) == 0) { strtoset_err(gettext("pic%d cannot measure event '%s' on this " "cpu\n"), picnum, val); return (-1); } nreqs++; (void) strncpy(reqs[picnum].cr_event, val, CPC_MAX_EVENT_LEN); return (0); } /* * We explicitly ignore any value provided for these tokens, as their * mere presence signals us to turn on or off the relevant flags. */ /*ARGSUSED*/ static int flag(int tok, char *val) { int i; int picnum = tok_info[tok].picnum; /* * If picnum is -1, this flag should be applied to all reqs. */ for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) { if (strcmp(tok_info[tok].name, "nouser") == 0) reqs[i].cr_flags &= ~CPC_COUNT_USER; else if (strcmp(tok_info[tok].name, "sys") == 0) reqs[i].cr_flags |= CPC_COUNT_SYSTEM; else return (-1); if (picnum != -1) break; } return (0); } static int doattr(int tok, char *val) { int i; int picnum = tok_info[tok].picnum; tmp_attr_t *tmp; char *endptr; /* * If picnum is -1, this attribute should be applied to all reqs. */ for (i = (picnum == -1) ? 0 : picnum; i < ncounters; i++) { tmp = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t)); tmp->name = tok_info[tok].name; if (val != NULL) { tmp->val = strtoll(val, &endptr, 0); if (endptr == val) { strtoset_err(gettext("invalid value '%s' for " "attribute '%s'\n"), val, tmp->name); free(tmp); return (-1); } } else /* * No value was provided for this attribute, * so specify a default value of 1. */ tmp->val = 1; tmp->next = attrs[i]; attrs[i] = tmp; reqs[i].cr_nattrs++; if (picnum != -1) break; } return (0); } /*ARGSUSED*/ static void attr_count_walker(void *arg, const char *attr) { /* * We don't allow picnum to be specified by the user. */ if (strncmp(attr, "picnum", 7) == 0) return; (*(int *)arg)++; } static int cpc_count_attrs(cpc_t *cpc) { int nattrs = 0; cpc_walk_attrs(cpc, &nattrs, attr_count_walker); return (nattrs); } static void attr_walker(void *arg, const char *attr) { int *i = arg; if (strncmp(attr, "picnum", 7) == 0) return; if ((attrlist[(*i)++] = strdup(attr)) == NULL) { strtoset_err(gettext("no memory available\n")); exit(0); } } cpc_set_t * cpc_strtoset(cpc_t *cpcin, const char *spec, int smt) { cpc_set_t *set; cpc_attr_t *req_attrs; tmp_attr_t *tmp; size_t toklen; int i; int j; int x; char *opts; char *val; cpc = cpcin; nattrs = 0; ncounters = cpc_npic(cpc); reqs = (request_t *)emalloc(ncounters * sizeof (request_t)); attrs = (tmp_attr_t **)emalloc(ncounters * sizeof (tmp_attr_t *)); for (i = 0; i < ncounters; i++) { reqs[i].cr_event[0] = '\0'; reqs[i].cr_flags = CPC_COUNT_USER; /* * Each pic will have at least one attribute: the physical pic * assignment via the "picnum" attribute. Set that up here for * each request. */ reqs[i].cr_nattrs = 1; attrs[i] = emalloc(sizeof (tmp_attr_t)); attrs[i]->name = "picnum"; attrs[i]->val = i; attrs[i]->next = NULL; } /* * Build up a list of acceptable tokens. * * Permitted tokens are * picn=event * nousern * sysn * attrn=val * nouser * sys * attr=val * * Where n is a counter number, and attr is any attribute supported by * the current processor. * * If a token appears without a counter number, it applies to all * counters in the request set. * * The number of tokens is: * * picn: ncounters * generic flags: 2 * ncounters (nouser, sys) * attrs: nattrs * ncounters * attrs with no picnum: nattrs * generic flags with no picnum: 2 (nouser, sys) * NULL token to signify end of list to getsubopt(3C). * * Matching each token's index in the token table is a function which * process that token; these are in tok_funcs. */ /* * Count the number of valid attributes. * Set up the attrlist array to point to the attributes in attrlistp. */ nattrs = cpc_count_attrs(cpc); attrlist = (char **)emalloc(nattrs * sizeof (char *)); i = 0; cpc_walk_attrs(cpc, &i, attr_walker); ntoks = ncounters + (2 * ncounters) + (nattrs * ncounters) + nattrs + 3; toks = (char **)emalloc(ntoks * sizeof (char *)); tok_info = (tok_info_t *)emalloc(ntoks * sizeof (tok_info_t)); tok_funcs = (int (**)(int, char *))emalloc(ntoks * sizeof (int (*)(char *))); for (i = 0; i < ntoks; i++) { toks[i] = NULL; tok_funcs[i] = NULL; } x = 0; for (i = 0; i < ncounters; i++) { toks[x] = (char *)emalloc(TOK_SIZE); (void) snprintf(toks[x], TOK_SIZE, "pic%d", i); tok_info[x].name = "pic"; tok_info[i].picnum = i; tok_funcs[x] = pic; x++; } for (i = 0; i < ncounters; i++) { toks[x] = (char *)emalloc(TOK_SIZE); (void) snprintf(toks[x], TOK_SIZE, "nouser%d", i); tok_info[x].name = "nouser"; tok_info[x].picnum = i; tok_funcs[x] = flag; x++; } for (i = 0; i < ncounters; i++) { toks[x] = (char *)emalloc(TOK_SIZE); (void) snprintf(toks[x], TOK_SIZE, "sys%d", i); tok_info[x].name = "sys"; tok_info[x].picnum = i; tok_funcs[x] = flag; x++; } for (j = 0; j < nattrs; j++) { toklen = strlen(attrlist[j]) + 3; for (i = 0; i < ncounters; i++) { toks[x] = (char *)emalloc(toklen); (void) snprintf(toks[x], toklen, "%s%d", attrlist[j], i); tok_info[x].name = attrlist[j]; tok_info[x].picnum = i; tok_funcs[x] = doattr; x++; } /* * Now create a token for this attribute with no picnum; if used * it will be applied to all reqs. */ toks[x] = (char *)emalloc(toklen); (void) snprintf(toks[x], toklen, "%s", attrlist[j]); tok_info[x].name = attrlist[j]; tok_info[x].picnum = -1; tok_funcs[x] = doattr; x++; } toks[x] = "nouser"; tok_info[x].name = "nouser"; tok_info[x].picnum = -1; tok_funcs[x] = flag; x++; toks[x] = "sys"; tok_info[x].name = "sys"; tok_info[x].picnum = -1; tok_funcs[x] = flag; x++; toks[x] = NULL; opts = strdupa(spec); while (*opts != '\0') { int idx = getsubopt(&opts, toks, &val); if (idx == -1) { if (find_event(val) == 0) { strtoset_err(gettext("bad token '%s'\n"), val); goto inval; } else continue; } if (tok_funcs[idx](idx, val) == -1) goto inval; } /* * The string has been processed. Now count how many PICs were used, * create a request set, and specify each request properly. */ if ((set = cpc_set_create(cpc)) == NULL) { strtoset_err(gettext("no memory available\n")); exit(0); } for (i = 0; i < ncounters; i++) { if (reqs[i].cr_event[0] == '\0') continue; /* * If the caller wishes to measure events on the physical CPU, * we need to add SMT attributes to each request. */ if (smt) smt_special(i); req_attrs = (cpc_attr_t *)emalloc(reqs[i].cr_nattrs * sizeof (cpc_attr_t)); j = 0; for (tmp = attrs[i]; tmp != NULL; tmp = tmp->next) { req_attrs[j].ca_name = tmp->name; req_attrs[j].ca_val = tmp->val; j++; } if (cpc_set_add_request(cpc, set, reqs[i].cr_event, 0, reqs[i].cr_flags, reqs[i].cr_nattrs, req_attrs) == -1) { free(req_attrs); (void) cpc_set_destroy(cpc, set); strtoset_err( gettext("cpc_set_add_request() failed: %s\n"), strerror(errno)); goto inval; } free(req_attrs); } strtoset_cleanup(); return (set); inval: strtoset_cleanup(); errno = EINVAL; return (NULL); } static void strtoset_cleanup(void) { int i; tmp_attr_t *tmp, *p; for (i = 0; i < nattrs; i++) free(attrlist[i]); free(attrlist); for (i = 0; i < ncounters; i++) { for (tmp = attrs[i]; tmp != NULL; tmp = p) { p = tmp->next; free(tmp); } } free(attrs); for (i = 0; i < ntoks - 3; i++) /* * We free all but the last three tokens: "nouser", "sys", NULL */ free(toks[i]); free(toks); free(reqs); free(tok_info); free(tok_funcs); } /* * The following is called to modify requests so that they count events on * behalf of a physical processor, instead of a logical processor. It duplicates * the request flags for the sibling processor (i.e. if the request counts user * events, add an attribute to count user events on the sibling processor also). */ static void smt_special(int picnum) { tmp_attr_t *attr; if (reqs[picnum].cr_flags & CPC_COUNT_USER) { attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t)); attr->name = "count_sibling_usr"; attr->val = 1; attr->next = attrs[picnum]; attrs[picnum] = attr; reqs[picnum].cr_nattrs++; } if (reqs[picnum].cr_flags & CPC_COUNT_SYSTEM) { attr = (tmp_attr_t *)emalloc(sizeof (tmp_attr_t)); attr->name = "count_sibling_sys"; attr->val = 1; attr->next = attrs[picnum]; attrs[picnum] = attr; reqs[picnum].cr_nattrs++; } } /* * If we ever fail to get memory, we print an error message and exit. */ static void * emalloc(size_t n) { void *p = malloc(n); if (p == NULL) { strtoset_err(gettext("no memory available\n")); exit(0); } return (p); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include "cpucmds.h" static hrtime_t timebase; void zerotime(void) { timebase = gethrtime(); } #define NSECPERSEC 1000000000.0 float mstimestamp(hrtime_t hrt) { if (hrt == 0) hrt = gethrtime(); return ((float)(hrt - timebase) / NSECPERSEC); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2025 Hans Rosenfeld PROG = cpustat OBJS = $(PROG).o caps.o time.o setgrp.o strtoset.o SRCS = $(OBJS:%.o=../common/%.c) CLEANFILES = $(OBJS) include ../../Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat CFLAGS += $(CCVERBOSE) $(CTF_FLAGS) CFLAGS64 += $(CCVERBOSE) $(CTF_FLAGS_64) CERRWARN += $(CNOWARN_UNINIT) CPPFLAGS += -D_REENTRANT -I$(SRC)/lib/libcpc/common LDLIBS += -lcpc -lkstat # not linted SMATCH=off .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) install: all $(ROOTUSRSBINPROG) clean: $(RM) $(CLEANFILES) strip: all $(STRIP) $(PROG) %.o: ../common/%.c $(COMPILE.c) $< $(POST_PROCESS_O) POFILES = ../common/$(PROG).po ../common/caps.po POFILE = $(PROG)_cmd.po $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ include ../../Makefile.targ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # include ../../Makefile.cmd include ../../Makefile.cmd.64 include ../../Makefile.ctf PROG = cputrack OBJS = $(PROG).o caps.o time.o setgrp.o strtoset.o LDLIBS += -lcpc -lpctx CFLAGS += $(CCVERBOSE) CPPFLAGS += -I$(SRC)/lib/libcpc/common POFILES = ../common/$(PROG).po ../common/caps.po POFILE = $(PROG)_cmd.po # not linted SMATCH=off # Hammerhead: ROOTLINK removed — ROOTBIN64 = ROOTBIN (path flattening) .KEEP_STATE: all: $(PROG) install: all $(ROOTPROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) %.o: ../common/%.c $(COMPILE.c) $< $(CTFCONVERT_O) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ include ../../Makefile.targ