/* * 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) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcpc_impl.h" #define MASK32 0xFFFFFFFF /* * The library uses the cpc_lock field of the cpc_t struct to protect access to * the linked lists inside the cpc_t, and only the linked lists. It is NOT used * to protect against users shooting themselves in the foot (such as, for * instance, destroying the same set at the same time from different threads.). * * SIGEMT needs to be blocked while holding the lock, to prevent deadlock among * an app holding the lock and a signal handler attempting to sample or bind. */ static char *cpc_get_list(int which, int arg); static void cpc_err(cpc_t *cpc, const char *fn, int subcode, ...); static int cpc_set_valid(cpc_t *cpc, cpc_set_t *set); static int cpc_lock(cpc_t *cpc); static void cpc_unlock(cpc_t *cpc, int blocked); static int cpc_valid_event(cpc_t *cpc, uint_t pic, const char *ev); static int cpc_valid_attr(cpc_t *cpc, char *attr); static void cpc_invalidate_pctx(cpc_t *cpc, pctx_t *pctx); cpc_t * cpc_open(int ver) { cpc_t *cpc; void (*sigsaved)(); int error = 0; int i; int j; if (ver != CPC_VER_CURRENT) { /* * v1 clients must stick to the v1 interface: cpc_version() */ errno = EINVAL; return (NULL); } /* * Call the syscall with invalid parameters. If we get ENOSYS this CPU * has no CPC support. We need to block SIGSYS because the syscall code * will send the signal if the system call fails to load. */ sigsaved = signal(SIGSYS, SIG_IGN); if (syscall(SYS_cpc, -1, -1, -1, -1, -1) != -1) { (void) signal(SIGSYS, sigsaved); errno = EINVAL; return (NULL); } error = errno; (void) signal(SIGSYS, sigsaved); if (error != EINVAL) { errno = error; return (NULL); } if ((cpc = malloc(sizeof (cpc_t))) == NULL) { errno = ENOMEM; return (NULL); } cpc->cpc_npic = syscall(SYS_cpc, CPC_NPIC, -1, 0, 0, 0); cpc->cpc_caps = syscall(SYS_cpc, CPC_CAPS, -1, 0, 0, 0); if (syscall(SYS_cpc, CPC_IMPL_NAME, -1, &cpc->cpc_cciname, 0, 0) != 0) return (NULL); if (syscall(SYS_cpc, CPC_CPUREF, -1, &cpc->cpc_cpuref, 0, 0) != 0) return (NULL); if ((cpc->cpc_attrlist = cpc_get_list(CPC_LIST_ATTRS, 0)) == NULL) { free(cpc); return (NULL); } if ((cpc->cpc_evlist = malloc(cpc->cpc_npic * sizeof (char *))) == NULL) { free(cpc->cpc_attrlist); free(cpc); return (NULL); } for (i = 0; i < cpc->cpc_npic; i++) { if ((cpc->cpc_evlist[i] = cpc_get_list(CPC_LIST_EVENTS, i)) == NULL) break; } if (i != cpc->cpc_npic) { for (j = 0; j < i; j++) free(cpc->cpc_evlist[j]); free(cpc->cpc_evlist); free(cpc->cpc_attrlist); free(cpc); return (NULL); } cpc->cpc_sets = NULL; cpc->cpc_bufs = NULL; cpc->cpc_errfn = NULL; (void) mutex_init(&cpc->cpc_lock, USYNC_THREAD, NULL); __pctx_cpc_register_callback(cpc_invalidate_pctx); return (cpc); } /* * Ensure state is cleaned up: * * - Hardware is unbound * - Sets are all destroyed * - Bufs are all freed */ int cpc_close(cpc_t *cpc) { while (cpc->cpc_sets != NULL) { if (cpc->cpc_sets->cs_state != CS_UNBOUND) (void) cpc_unbind(cpc, cpc->cpc_sets); (void) cpc_set_destroy(cpc, cpc->cpc_sets); } while (cpc->cpc_bufs != NULL) (void) cpc_buf_destroy(cpc, cpc->cpc_bufs); free(cpc); return (0); } /* * Terminate everything that runs in pctx_run */ void cpc_terminate(cpc_t *cpc) { cpc_set_t *csp; int sigblocked; sigblocked = cpc_lock(cpc); for (csp = cpc->cpc_sets; csp != NULL; csp = csp->cs_next) { if (csp->cs_pctx != NULL) pctx_terminate(csp->cs_pctx); } cpc_unlock(cpc, sigblocked); } cpc_set_t * cpc_set_create(cpc_t *cpc) { cpc_set_t *set; int sigblocked; if ((set = malloc(sizeof (*set))) == NULL) { errno = ENOMEM; return (NULL); } set->cs_request = NULL; set->cs_nreqs = 0; set->cs_state = CS_UNBOUND; set->cs_fd = -1; set->cs_pctx = NULL; set->cs_id = -1; set->cs_thr = 0; sigblocked = cpc_lock(cpc); set->cs_next = cpc->cpc_sets; cpc->cpc_sets = set; cpc_unlock(cpc, sigblocked); return (set); } int cpc_set_destroy(cpc_t *cpc, cpc_set_t *set) { cpc_set_t *csp, *prev; cpc_request_t *req, *next; int sigblocked; /* * Remove this set from the cpc handle's list of sets. */ sigblocked = cpc_lock(cpc); for (csp = prev = cpc->cpc_sets; csp != NULL; csp = csp->cs_next) { if (csp == set) break; prev = csp; } if (csp == NULL) { cpc_unlock(cpc, sigblocked); errno = EINVAL; return (-1); } if (csp == cpc->cpc_sets) cpc->cpc_sets = csp->cs_next; prev->cs_next = csp->cs_next; cpc_unlock(cpc, sigblocked); if (csp->cs_state != CS_UNBOUND) (void) cpc_unbind(cpc, csp); /* * Detach from the process */ if (csp->cs_pctx != NULL) { pctx_release(csp->cs_pctx); csp->cs_pctx = NULL; } for (req = csp->cs_request; req != NULL; req = next) { next = req->cr_next; if (req->cr_nattrs != 0) free(req->cr_attr); free(req); } free(set); return (0); } /*ARGSUSED*/ int cpc_set_add_request(cpc_t *cpc, cpc_set_t *set, const char *event, uint64_t preset, uint_t flags, uint_t nattrs, const cpc_attr_t *attrs) { cpc_request_t *req; const char *fn = "cpc_set_add_request"; int i; int npics = cpc_npic(cpc); if (cpc_set_valid(cpc, set) != 0 || set->cs_state != CS_UNBOUND) { errno = EINVAL; return (-1); } for (i = 0; i < npics; i++) if (cpc_valid_event(cpc, i, event)) break; if (i == npics) { cpc_err(cpc, fn, CPC_INVALID_EVENT); errno = EINVAL; return (-1); } if ((req = malloc(sizeof (*req))) == NULL) { errno = ENOMEM; return (-1); } (void) strncpy(req->cr_event, event, CPC_MAX_EVENT_LEN); req->cr_preset = preset; req->cr_flags = flags; req->cr_nattrs = nattrs; req->cr_index = set->cs_nreqs; req->cr_attr = NULL; if (nattrs != 0) { for (i = 0; i < nattrs; i++) { /* * Verify that each attribute name is legal and valid. */ if (attrs[i].ca_name[0] == '\0' || cpc_valid_attr(cpc, attrs[i].ca_name) == 0) { cpc_err(cpc, fn, CPC_INVALID_ATTRIBUTE); goto inval; } /* * If the user requested a specific picnum, ensure that * the pic can count the requested event. */ if (strncmp("picnum", attrs[i].ca_name, 8) == 0) { if (attrs[i].ca_val >= npics) { cpc_err(cpc, fn, CPC_INVALID_PICNUM); goto inval; } if (cpc_valid_event(cpc, attrs[i].ca_val, req->cr_event) == 0) { cpc_err(cpc, fn, CPC_PIC_NOT_CAPABLE); goto inval; } } } if ((req->cr_attr = malloc(nattrs * sizeof (kcpc_attr_t))) == NULL) { free(req); return (-1); } for (i = 0; i < nattrs; i++) { req->cr_attr[i].ka_val = attrs[i].ca_val; (void) strncpy(req->cr_attr[i].ka_name, attrs[i].ca_name, CPC_MAX_ATTR_LEN); } } else req->cr_attr = NULL; req->cr_next = set->cs_request; set->cs_request = req; set->cs_nreqs++; return (req->cr_index); inval: free(req); errno = EINVAL; return (-1); } cpc_buf_t * cpc_buf_create(cpc_t *cpc, cpc_set_t *set) { cpc_buf_t *buf; int sigblocked; if (cpc_set_valid(cpc, set) != 0) { errno = EINVAL; return (NULL); } if ((buf = malloc(sizeof (*buf))) == NULL) return (NULL); buf->cb_size = set->cs_nreqs * sizeof (uint64_t); if ((buf->cb_data = malloc(buf->cb_size)) == NULL) { free(buf); return (NULL); } bzero(buf->cb_data, buf->cb_size); buf->cb_hrtime = 0; buf->cb_tick = 0; sigblocked = cpc_lock(cpc); buf->cb_next = cpc->cpc_bufs; cpc->cpc_bufs = buf; cpc_unlock(cpc, sigblocked); return (buf); } int cpc_buf_destroy(cpc_t *cpc, cpc_buf_t *buf) { cpc_buf_t *cbp, *prev; int sigblocked; /* * Remove this buf from the cpc handle's list of bufs. */ sigblocked = cpc_lock(cpc); for (cbp = prev = cpc->cpc_bufs; cbp != NULL; cbp = cbp->cb_next) { if (cbp == buf) break; prev = cbp; } if (cbp == NULL) { cpc_unlock(cpc, sigblocked); errno = EINVAL; return (-1); } if (cbp == cpc->cpc_bufs) cpc->cpc_bufs = cbp->cb_next; prev->cb_next = cbp->cb_next; cpc_unlock(cpc, sigblocked); free(cbp->cb_data); free(cbp); return (0); } /*ARGSUSED*/ int cpc_bind_curlwp(cpc_t *cpc, cpc_set_t *set, uint_t flags) { char *packed_set; size_t packsize; int ret; int subcode = -1; /* * We don't bother checking cpc_set_valid() here, because this is in the * fast path of an app doing SIGEMT-based profiling as they restart the * counters from their signal handler. */ if (CPC_SET_VALID_FLAGS(flags) == 0 || set->cs_nreqs <= 0) { errno = EINVAL; return (-1); } if ((packed_set = __cpc_pack_set(set, flags, &packsize)) == NULL) { errno = ENOMEM; return (-1); } ret = syscall(SYS_cpc, CPC_BIND, -1, packed_set, packsize, &subcode); free(packed_set); if (ret != 0) { if (subcode != -1) cpc_err(cpc, "cpc_bind_curlwp", subcode); return (-1); } set->cs_thr = thr_self(); set->cs_state = CS_BOUND_CURLWP; return (ret); } /*ARGSUSED*/ int cpc_bind_pctx(cpc_t *cpc, pctx_t *pctx, id_t id, cpc_set_t *set, uint_t flags) { char *packed_set; size_t packsize; int ret; int subcode = -1; /* * cpc_bind_pctx() currently has no valid flags. */ if (flags != 0 || cpc_set_valid(cpc, set) != 0 || set->cs_nreqs <= 0) { errno = EINVAL; return (-1); } if ((packed_set = __cpc_pack_set(set, flags, &packsize)) == NULL) { errno = ENOMEM; return (-1); } ret = __pctx_cpc(pctx, cpc, CPC_BIND, id, packed_set, (void *)packsize, (void *)&subcode, -1); free(packed_set); if (ret == 0) { set->cs_pctx = pctx; set->cs_id = id; set->cs_state = CS_BOUND_PCTX; } else if (subcode != -1) cpc_err(cpc, "cpc_bind_pctx", subcode); return (ret); } /*ARGSUSED*/ int cpc_bind_cpu(cpc_t *cpc, processorid_t id, cpc_set_t *set, uint_t flags) { int fd; char *packed_set; size_t packsize; __cpc_args_t cpc_args; int error; const char *fn = "cpc_bind_cpu"; int subcode = -1; /* * cpc_bind_cpu() currently has no valid flags. */ if (flags != 0 || cpc_set_valid(cpc, set) != 0 || set->cs_nreqs <= 0) { errno = EINVAL; return (-1); } if (processor_bind(P_LWPID, P_MYID, id, &set->cs_obind) == -1) { cpc_err(cpc, fn, CPC_PBIND_FAILED); return (-1); } if ((fd = open(CPUDRV_SHARED, O_RDWR)) < 0) { error = errno; (void) processor_bind(P_LWPID, P_MYID, set->cs_obind, NULL); errno = error; return (-1); } /* * To avoid leaking file descriptors, if we find an existing fd here we * just close it. This is only a problem if a user attempts to bind the * same set to different CPUs without first unbinding it. */ if (set->cs_fd != -1) (void) close(set->cs_fd); set->cs_fd = fd; if ((packed_set = __cpc_pack_set(set, flags, &packsize)) == NULL) { (void) close(fd); (void) processor_bind(P_LWPID, P_MYID, set->cs_obind, NULL); errno = ENOMEM; return (-1); } cpc_args.udata1 = packed_set; cpc_args.udata2 = (void *)packsize; cpc_args.udata3 = (void *)&subcode; if (ioctl(fd, CPCIO_BIND, &cpc_args) != 0) { error = errno; free(packed_set); (void) close(fd); (void) processor_bind(P_LWPID, P_MYID, set->cs_obind, NULL); if (subcode != -1) cpc_err(cpc, fn, subcode); errno = error; return (-1); } free(packed_set); set->cs_thr = thr_self(); set->cs_state = CS_BOUND_CPU; return (0); } /*ARGSUSED*/ int cpc_request_preset(cpc_t *cpc, int index, uint64_t preset) { return (syscall(SYS_cpc, CPC_PRESET, -1, index, (uint32_t)(preset >> 32), (uint32_t)(preset & MASK32))); } /*ARGSUSED*/ int cpc_set_restart(cpc_t *cpc, cpc_set_t *set) { return (syscall(SYS_cpc, CPC_RESTART, -1, 0, 0, 0)); } /*ARGSUSED*/ int cpc_unbind(cpc_t *cpc, cpc_set_t *set) { int ret = 0; int error; if (cpc_set_valid(cpc, set) != 0) { errno = EINVAL; return (-1); } switch (set->cs_state) { case CS_UNBOUND: errno = EINVAL; return (-1); case CS_BOUND_CURLWP: ret = syscall(SYS_cpc, CPC_RELE, -1, 0, 0, 0); error = errno; break; case CS_BOUND_CPU: ret = ioctl(set->cs_fd, CPCIO_RELE, NULL); error = errno; (void) close(set->cs_fd); set->cs_fd = -1; (void) processor_bind(P_LWPID, P_MYID, set->cs_obind, NULL); break; case CS_BOUND_PCTX: if (set->cs_pctx != NULL) { ret = __pctx_cpc(set->cs_pctx, cpc, CPC_RELE, set->cs_id, 0, 0, 0, 0); error = errno; } break; } set->cs_thr = 0; set->cs_id = -1; set->cs_state = CS_UNBOUND; if (ret != 0) errno = error; return (ret); } /*ARGSUSED*/ int cpc_set_sample(cpc_t *cpc, cpc_set_t *set, cpc_buf_t *buf) { __cpc_args_t args; /* * The following check ensures that only the most recently bound set * can be sampled, as binding a set invalidates all other sets in the * cpc_t. */ if (set->cs_state == CS_UNBOUND || buf->cb_size != set->cs_nreqs * sizeof (uint64_t)) { errno = EINVAL; return (-1); } switch (set->cs_state) { case CS_BOUND_CURLWP: return (syscall(SYS_cpc, CPC_SAMPLE, -1, buf->cb_data, &buf->cb_hrtime, &buf->cb_tick)); case CS_BOUND_CPU: args.udata1 = buf->cb_data; args.udata2 = &buf->cb_hrtime; args.udata3 = &buf->cb_tick; return (ioctl(set->cs_fd, CPCIO_SAMPLE, &args)); case CS_BOUND_PCTX: return (__pctx_cpc(set->cs_pctx, cpc, CPC_SAMPLE, set->cs_id, buf->cb_data, &buf->cb_hrtime, &buf->cb_tick, buf->cb_size)); } errno = EINVAL; return (-1); } /*ARGSUSED*/ void cpc_buf_sub(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *a, cpc_buf_t *b) { int i; if (a->cb_size != ds->cb_size || b->cb_size != ds->cb_size) return; ds->cb_hrtime = (a->cb_hrtime > b->cb_hrtime) ? a->cb_hrtime : b->cb_hrtime; ds->cb_tick = a->cb_tick - b->cb_tick; for (i = 0; i < ds->cb_size / sizeof (uint64_t); i++) ds->cb_data[i] = a->cb_data[i] - b->cb_data[i]; } /*ARGSUSED*/ void cpc_buf_add(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *a, cpc_buf_t *b) { int i; if (a->cb_size != ds->cb_size || b->cb_size != ds->cb_size) return; ds->cb_hrtime = (a->cb_hrtime > b->cb_hrtime) ? a->cb_hrtime : b->cb_hrtime; ds->cb_tick = a->cb_tick + b->cb_tick; for (i = 0; i < ds->cb_size / sizeof (uint64_t); i++) ds->cb_data[i] = a->cb_data[i] + b->cb_data[i]; } /*ARGSUSED*/ void cpc_buf_copy(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *src) { if (ds->cb_size != src->cb_size) return; bcopy(src->cb_data, ds->cb_data, ds->cb_size); ds->cb_hrtime = src->cb_hrtime; ds->cb_tick = src->cb_tick; } /*ARGSUSED*/ void cpc_buf_zero(cpc_t *cpc, cpc_buf_t *buf) { bzero(buf->cb_data, buf->cb_size); buf->cb_hrtime = 0; buf->cb_tick = 0; } /* * Gets or sets the value of the request specified by index. */ /*ARGSUSED*/ int cpc_buf_get(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val) { *val = buf->cb_data[index]; return (0); } /*ARGSUSED*/ int cpc_buf_set(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t val) { buf->cb_data[index] = val; return (0); } /*ARGSUSED*/ hrtime_t cpc_buf_hrtime(cpc_t *cpc, cpc_buf_t *buf) { return (buf->cb_hrtime); } /*ARGSUSED*/ uint64_t cpc_buf_tick(cpc_t *cpc, cpc_buf_t *buf) { return (buf->cb_tick); } static char * cpc_get_list(int which, int arg) { int szcmd; int size; char *list; if (which == CPC_LIST_ATTRS) szcmd = CPC_ATTRLIST_SIZE; else szcmd = CPC_EVLIST_SIZE; if (syscall(SYS_cpc, szcmd, -1, &size, arg, 0) != 0) return (NULL); if ((list = malloc(size)) == NULL) return (NULL); if (syscall(SYS_cpc, which, -1, list, arg, 0) != 0) { free(list); return (NULL); } return (list); } /*ARGSUSED*/ void cpc_walk_requests(cpc_t *cpc, cpc_set_t *set, void *arg, void (*action)(void *arg, int index, const char *event, uint64_t preset, uint_t flags, int nattrs, const cpc_attr_t *attrs)) { cpc_request_t *rp; cpc_attr_t *attrs = NULL; int i; for (rp = set->cs_request; rp != NULL; rp = rp->cr_next) { /* * Need to reconstruct a temporary cpc_attr_t array for req. */ if (rp->cr_nattrs != 0) if ((attrs = malloc(rp->cr_nattrs * sizeof (cpc_attr_t))) == NULL) return; for (i = 0; i < rp->cr_nattrs; i++) { attrs[i].ca_name = rp->cr_attr[i].ka_name; attrs[i].ca_val = rp->cr_attr[i].ka_val; } action(arg, rp->cr_index, rp->cr_event, rp->cr_preset, rp->cr_flags, rp->cr_nattrs, attrs); if (rp->cr_nattrs != 0) free(attrs); } } /*ARGSUSED*/ static void cpc_walk_events_impl(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event), int is_generic) { char **list; char *p, *e; int i; int is_papi; int ncounters = cpc_npic(cpc); cpc_strhash_t *hash; if ((list = malloc(ncounters * sizeof (char *))) == NULL) return; if ((hash = __cpc_strhash_alloc()) == NULL) { free(list); return; } for (i = 0; i < ncounters; i++) { if ((list[i] = strdup(cpc->cpc_evlist[i])) == NULL) goto err; p = list[i]; while ((e = strchr(p, ',')) != NULL) { *e = '\0'; /* * Based on is_generic flag, skip appropriate * event names. */ is_papi = (strncmp(p, "PAPI", 4) == 0); if (is_generic != is_papi) { p = e + 1; continue; } if (__cpc_strhash_add(hash, p) == -1) goto err; p = e + 1; } is_papi = (strncmp(p, "PAPI", 4) == 0); if (is_generic == is_papi) { if (__cpc_strhash_add(hash, p) == -1) goto err; } } while ((p = __cpc_strhash_next(hash)) != NULL) action(arg, p); err: __cpc_strhash_free(hash); for (i = 0; i < ncounters; i++) free(list[i]); free(list); } /*ARGSUSED*/ void cpc_walk_events_all(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event)) { cpc_walk_events_impl(cpc, arg, action, 0); } /*ARGSUSED*/ void cpc_walk_generic_events_all(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event)) { cpc_walk_events_impl(cpc, arg, action, 1); } /*ARGSUSED*/ static void cpc_walk_events_pic_impl(cpc_t *cpc, uint_t picno, void *arg, void (*action)(void *arg, uint_t picno, const char *event), int is_generic) { char *p; char *e; char *list; int is_papi; if (picno >= cpc->cpc_npic) { errno = EINVAL; return; } if ((list = strdup(cpc->cpc_evlist[picno])) == NULL) return; /* * List now points to a comma-separated list of events supported by * the designated pic. */ p = list; while ((e = strchr(p, ',')) != NULL) { *e = '\0'; /* * Based on is_generic flag, skip appropriate * event names. */ is_papi = (strncmp(p, "PAPI", 4) == 0); if (is_generic != is_papi) { p = e + 1; continue; } action(arg, picno, p); p = e + 1; } is_papi = (strncmp(p, "PAPI", 4) == 0); if (is_generic == is_papi) action(arg, picno, p); free(list); } /*ARGSUSED*/ void cpc_walk_events_pic(cpc_t *cpc, uint_t picno, void *arg, void (*action)(void *arg, uint_t picno, const char *event)) { cpc_walk_events_pic_impl(cpc, picno, arg, action, 0); } /*ARGSUSED*/ void cpc_walk_generic_events_pic(cpc_t *cpc, uint_t picno, void *arg, void (*action)(void *arg, uint_t picno, const char *event)) { cpc_walk_events_pic_impl(cpc, picno, arg, action, 1); } /*ARGSUSED*/ void cpc_walk_attrs(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *attr)) { char *p; char *e; char *list; if ((list = strdup(cpc->cpc_attrlist)) == NULL) return; /* * Platforms with no attributes will return an empty string. */ if (*list == '\0') return; /* * List now points to a comma-separated list of attributes supported by * the underlying platform. */ p = list; while ((e = strchr(p, ',')) != NULL) { *e = '\0'; action(arg, p); p = e + 1; } action(arg, p); free(list); } /*ARGSUSED*/ int cpc_enable(cpc_t *cpc) { return (syscall(SYS_cpc, CPC_ENABLE, -1, 0, 0, 0)); } /*ARGSUSED*/ int cpc_disable(cpc_t *cpc) { return (syscall(SYS_cpc, CPC_DISABLE, -1, 0, 0, 0)); } /*ARGSUSED*/ uint_t cpc_npic(cpc_t *cpc) { return (cpc->cpc_npic); } /*ARGSUSED*/ uint_t cpc_caps(cpc_t *cpc) { return (cpc->cpc_caps); } const char * cpc_cciname(cpc_t *cpc) { return (cpc->cpc_cciname); } const char * cpc_cpuref(cpc_t *cpc) { return (cpc->cpc_cpuref); } int cpc_seterrhndlr(cpc_t *cpc, cpc_errhndlr_t *fn) { cpc->cpc_errfn = fn; return (0); } /* * These strings may contain printf() conversion specifiers. */ static const char *errstr[] = { "", /* zero slot filler */ "Unknown event\n", /* CPC_INVALID_EVENT */ "Invalid counter number\n", /* CPC_INVALID_PICNUM */ "Unknown attribute\n", /* CPC_INVALID_ATTRIBUTE */ "Attribute out of range\n", /* CPC_ATTRIBUTE_OUT_OF_RANGE */ "Hardware resource unavailable\n", /* CPC_RESOURCE_UNAVAIL */ "Counter cannot count requested event\n", /* CPC_PIC_NOT_CAPABLE */ "Invalid flags in a request\n", /* CPC_REQ_INVALID_FLAGS */ "Requests conflict with each other\n", /* CPC_CONFLICTING_REQS */ "Attribute requires the cpc_cpu privilege\n", /* CPC_ATTR_REQUIRES_PRIVILEGE */ "Couldn't bind LWP to requested processor\n", /* CPC_PBIND_FAILED */ "Hypervisor event access denied\n" /* CPC_HV_NO_ACCESS */ }; /*VARARGS3*/ static void cpc_err(cpc_t *cpc, const char *fn, int subcode, ...) { va_list ap; const char *str; int error; /* * If subcode is -1, there is no specific description for this error. */ if (subcode == -1) return; /* * We need to preserve errno across calls to this function to prevent it * from being clobbered while here, or in the user's error handler. */ error = errno; str = dgettext(TEXT_DOMAIN, errstr[subcode]); va_start(ap, subcode); if (cpc->cpc_errfn != NULL) cpc->cpc_errfn(fn, subcode, str, ap); else { /* * If printf() conversion specifiers are added to the errstr[] * table, this call needs to be changed to vfprintf(). */ (void) fprintf(stderr, "libcpc: %s: %s", fn, str); } va_end(ap); errno = error; } /* * Hook used by libpctx to alert libcpc when a pctx handle is going away. * This is necessary to prevent libcpc from attempting a libpctx operation on a * stale and invalid pctx_t handle. Since pctx_t's are cached by libcpc, we need * to be notified when they go away. */ static void cpc_invalidate_pctx(cpc_t *cpc, pctx_t *pctx) { cpc_set_t *set; int sigblocked; sigblocked = cpc_lock(cpc); for (set = cpc->cpc_sets; set != NULL; set = set->cs_next) if (set->cs_pctx == pctx) set->cs_pctx = NULL; cpc_unlock(cpc, sigblocked); } /* * Check that the set is valid; if so it will be in the cpc handle's * list of sets. The lock protects the list of sets, but not the set * itself. */ static int cpc_set_valid(cpc_t *cpc, cpc_set_t *set) { cpc_set_t *csp; int sigblocked; sigblocked = cpc_lock(cpc); for (csp = cpc->cpc_sets; csp != NULL; csp = csp->cs_next) if (csp == set) break; cpc_unlock(cpc, sigblocked); if (csp == NULL) return (-1); return (0); } static int cpc_lock(cpc_t *cpc) { int ret = (sigset(SIGEMT, SIG_HOLD) == SIG_HOLD); (void) mutex_lock(&cpc->cpc_lock); return (ret); } static void cpc_unlock(cpc_t *cpc, int sigblocked) { (void) mutex_unlock(&cpc->cpc_lock); if (sigblocked == 0) (void) sigrelse(SIGEMT); } struct priv { const char *name; int found; }; /*ARGSUSED*/ static void ev_walker(void *arg, uint_t picno, const char *ev) { if (strcmp(((struct priv *)arg)->name, ev) == 0) ((struct priv *)arg)->found = 1; } static void at_walker(void *arg, const char *at) { if (strcmp(((struct priv *)arg)->name, at) == 0) ((struct priv *)arg)->found = 1; } static int cpc_valid_event(cpc_t *cpc, uint_t pic, const char *ev) { struct priv pr = { NULL, 0 }; char *end_ev; int err; pr.name = ev; cpc_walk_events_pic(cpc, pic, &pr, ev_walker); if (pr.found) return (1); cpc_walk_generic_events_pic(cpc, pic, &pr, ev_walker); if (pr.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(ev, &end_ev, 0); if ((errno == 0) && (*end_ev == '\0')) { /* * Success - this is a valid raw code in hex, decimal, or octal. */ errno = err; return (1); } errno = err; return (0); } static int cpc_valid_attr(cpc_t *cpc, char *attr) { struct priv pr = { NULL, 0 }; pr.name = attr; cpc_walk_attrs(cpc, &pr, at_walker); return (pr.found); } /* * 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. */ #ifndef _LIBCPC_H #define _LIBCPC_H #include #include #include #include #include #include #include #include #include /* * This library allows hardware performance counters present in * certain processors to be used by applications to monitor their * own statistics, the statistics of others, or the statistics of a given CPU. */ #ifdef __cplusplus extern "C" { #endif typedef struct __cpc cpc_t; typedef struct __cpc_set cpc_set_t; typedef struct __cpc_buf cpc_buf_t; /* * Current library version must be passed to cpc_open(). */ #define CPC_VER_CURRENT 2 /* * Initializes the library for use and returns a pointer to an identifier that * must be used as the cpc argument in subsequent libcpc calls. */ extern cpc_t *cpc_open(int ver); extern int cpc_close(cpc_t *cpc); /* * Query information about the underlying processor. */ extern uint_t cpc_npic(cpc_t *cpc); extern uint_t cpc_caps(cpc_t *cpc); extern const char *cpc_cciname(cpc_t *cpc); extern const char *cpc_cpuref(cpc_t *cpc); /* * A vprintf-like error handling routine can be passed to the * library for use by more sophisticated callers. * If specified as NULL, errors are written to stderr. */ typedef void (cpc_errhndlr_t)(const char *fn, int subcode, const char *fmt, va_list ap); extern int cpc_seterrhndlr(cpc_t *cpc, cpc_errhndlr_t *fn); extern cpc_set_t *cpc_set_create(cpc_t *cpc); extern int cpc_set_destroy(cpc_t *cpc, cpc_set_t *set); /* * If successful, returns an index for the new request within the set which is * needed later to retrieve the request's data. * Returns -1 if unsuccessful and sets errno to indicate the error. */ extern int cpc_set_add_request(cpc_t *cpc, cpc_set_t *set, const char *event, uint64_t preset, uint_t flags, uint_t nattrs, const cpc_attr_t *attrs); extern cpc_buf_t *cpc_buf_create(cpc_t *cpc, cpc_set_t *set); extern int cpc_buf_destroy(cpc_t *cpc, cpc_buf_t *buf); /* * Binds the set to the current LWP. */ extern int cpc_bind_curlwp(cpc_t *cpc, cpc_set_t *set, uint_t flags); /* * Binds the set to the specified LWP in a process controlled via libpctx. */ extern int cpc_bind_pctx(cpc_t *cpc, pctx_t *pctx, id_t id, cpc_set_t *set, uint_t flags); /* * Binds the set to the specified CPU. The process must have sufficient * privileges to bind to the CPU via processor_bind(2). An LWP can only * bind to one CPU at a time. To measure more than one CPU simultaneously, * one LWP must be created for each CPU. */ extern int cpc_bind_cpu(cpc_t *cpc, processorid_t id, cpc_set_t *set, uint_t flags); /* * Set the starting value for the indexed counter, and restart counting for a * set that was frozen by a counter overflow. */ extern int cpc_request_preset(cpc_t *cpc, int index, uint64_t preset); extern int cpc_set_restart(cpc_t *cpc, cpc_set_t *set); /* * Unbinds the set and frees up associated resources. cpc_buf_t's must be * explicitly freed via cpc_buf_destroy(). */ extern int cpc_unbind(cpc_t *cpc, cpc_set_t *set); /* * Samples a set into a cpc_buf_t. The provided set must be bound, and the * buf must have been created with the set being sampled. */ extern int cpc_set_sample(cpc_t *cpc, cpc_set_t *set, cpc_buf_t *buf); extern void cpc_buf_sub(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *a, cpc_buf_t *b); extern void cpc_buf_add(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *a, cpc_buf_t *b); extern void cpc_buf_copy(cpc_t *cpc, cpc_buf_t *ds, cpc_buf_t *src); extern void cpc_buf_zero(cpc_t *cpc, cpc_buf_t *buf); /* * Gets or sets the value of the request specified by index. */ extern int cpc_buf_get(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val); extern int cpc_buf_set(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t val); extern hrtime_t cpc_buf_hrtime(cpc_t *cpc, cpc_buf_t *buf); extern uint64_t cpc_buf_tick(cpc_t *cpc, cpc_buf_t *buf); extern void cpc_walk_requests(cpc_t *cpc, cpc_set_t *set, void *arg, void (*action)(void *arg, int index, const char *event, uint64_t preset, uint_t flags, int nattrs, const cpc_attr_t *attrs)); extern void cpc_walk_events_all(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event)); extern void cpc_walk_generic_events_all(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event)); extern void cpc_walk_events_pic(cpc_t *cpc, uint_t picno, void *arg, void (*action)(void *arg, uint_t picno, const char *event)); extern void cpc_walk_generic_events_pic(cpc_t *cpc, uint_t picno, void *arg, void (*action)(void *arg, uint_t picno, const char *event)); extern void cpc_walk_attrs(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *attr)); extern int cpc_enable(cpc_t *cpc); extern int cpc_disable(cpc_t *cpc); extern void cpc_terminate(cpc_t *); #if defined(__sparc) || defined(__i386) /* * Obsolete libcpc interfaces. */ #define CPC_VER_NONE 0 typedef struct _cpc_event cpc_event_t; extern uint_t cpc_version(uint_t ver); extern int cpc_access(); extern int cpc_getcpuver(void); extern const char *cpc_getcciname(int cpuver); extern const char *cpc_getcpuref(int cpuver); extern uint_t cpc_getnpic(int cpuver); typedef void (cpc_errfn_t)(const char *fn, const char *fmt, va_list ap); extern void cpc_seterrfn(cpc_errfn_t *errfn); extern const char *cpc_getusage(int cpuver); extern void cpc_walk_names(int cpuver, int regno, void *arg, void (*action)(void *arg, int regno, const char *name, uint8_t bits)); extern int cpc_strtoevent(int cpuver, const char *spec, cpc_event_t *event); extern char *cpc_eventtostr(cpc_event_t *event); extern void cpc_event_accum(cpc_event_t *accum, cpc_event_t *event); extern void cpc_event_diff(cpc_event_t *diff, cpc_event_t *left, cpc_event_t *right); extern int cpc_bind_event(cpc_event_t *event, int flags); extern int cpc_take_sample(cpc_event_t *event); extern int cpc_count_usr_events(int enable); extern int cpc_count_sys_events(int enable); extern int cpc_rele(void); extern int cpc_pctx_bind_event(pctx_t *pctx, id_t lwpid, cpc_event_t *event, int flags); extern int cpc_pctx_take_sample(pctx_t *pctx, id_t lwpid, cpc_event_t *event); extern int cpc_pctx_rele(pctx_t *pctx, id_t lwpid); extern int cpc_pctx_invalidate(pctx_t *pctx, id_t lwpid); extern int cpc_shared_open(void); extern void cpc_shared_close(int fd); extern int cpc_shared_bind_event(int fd, cpc_event_t *event, int flags); extern int cpc_shared_take_sample(int fd, cpc_event_t *event); extern int cpc_shared_rele(int fd); #endif /* __sparc || __i386 */ #ifdef __cplusplus } #endif #endif /* _LIBCPC_H */ /* * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _LIBCPC_IMPL_H #define _LIBCPC_IMPL_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define CPC_VER_1 1 #define CPC1_BUFSIZE (2 * sizeof (uint64_t)) struct _cpc_attr { char ca_name[CPC_MAX_ATTR_LEN]; uint64_t ca_val; }; typedef struct __cpc_request cpc_request_t; struct __cpc_request { char cr_event[CPC_MAX_EVENT_LEN]; uint64_t cr_preset; /* Initial value */ uint16_t cr_index; /* Index of request in data */ uint_t cr_flags; uint_t cr_nattrs; /* # CPU-specific attrs */ kcpc_attr_t *cr_attr; cpc_request_t *cr_next; /* next request in set */ }; struct __cpc_buf { uint64_t *cb_data; /* Pointer to data store */ hrtime_t cb_hrtime; /* hrtime at last sample */ uint64_t cb_tick; /* virtualized tsc/tick */ size_t cb_size; /* Size of data store, bytes */ cpc_buf_t *cb_next; /* List of all bufs */ }; /* * Possible cpc_set_t states: */ typedef enum { CS_UNBOUND, /* Set is not currently bound */ CS_BOUND_CURLWP, /* Set has been bound to curlwp */ CS_BOUND_PCTX, /* Set has been bound via libpctx */ CS_BOUND_CPU /* Set has been bound to a CPU */ } __cpc_state_t; struct __cpc_set { cpc_request_t *cs_request; /* linked list of requests */ __cpc_state_t cs_state; /* State of this set */ int cs_nreqs; /* Number of requests in set */ int cs_fd; /* file descriptor of cpc dev */ processorid_t cs_obind; /* previous proc binding */ pctx_t *cs_pctx; /* pctx of process bound to */ id_t cs_id; /* lwp ID of pctx binding */ thread_t cs_thr; /* thread ID which bound set */ cpc_set_t *cs_next; /* Linked list of all sets */ }; struct __cpc { cpc_set_t *cpc_sets; /* List of existing sets */ cpc_buf_t *cpc_bufs; /* List of existing bufs */ cpc_errhndlr_t *cpc_errfn; /* Handles library errors */ mutex_t cpc_lock; /* Protect various ops */ char *cpc_attrlist; /* List of supported attrs */ char **cpc_evlist; /* List of events per pic */ char cpc_cpuref[CPC_MAX_CPUREF]; char cpc_cciname[CPC_MAX_IMPL_NAME]; uint_t cpc_caps; uint_t cpc_npic; }; /* * cpc_t handle for CPCv1 clients. */ extern cpc_t *__cpc; /*PRINTFLIKE2*/ extern void __cpc_error(const char *fn, const char *fmt, ...); extern const char *__cpc_reg_to_name(int cpuver, int regno, uint8_t bits); extern int __cpc_name_to_reg(int cpuver, int regno, const char *name, uint8_t *bits); extern uint_t __cpc_workver; extern int __cpc_v1_cpuver; #ifdef __sparc extern uint64_t __cpc_v1_pcr; #else extern uint32_t __cpc_v1_pes[2]; #endif /* __sparc */ extern char *__cpc_pack_set(cpc_set_t *set, uint_t flags, size_t *buflen); typedef struct __cpc_strhash cpc_strhash_t; struct __cpc_strhash { char *str; struct __cpc_strhash *cur; struct __cpc_strhash *next; }; extern cpc_strhash_t *__cpc_strhash_alloc(void); extern void __cpc_strhash_free(cpc_strhash_t *hash); extern int __cpc_strhash_add(cpc_strhash_t *hash, char *key); extern char *__cpc_strhash_next(cpc_strhash_t *hash); /* * Implementation-private system call used by libcpc */ struct __cpc; extern int __pctx_cpc(pctx_t *pctx, struct __cpc *cpc, int cmd, id_t lwpid, void *data1, void *data2, void *data3, int bufsize); #define CPUDRV "/devices/pseudo/cpc@0" #define CPUDRV_SHARED CPUDRV":shared" #if defined(__sparc) || defined(__i386) /* * These two are only used for backwards compatibility to the Obsolete CPCv1. */ extern int __cpc_init(void); extern cpc_set_t *__cpc_eventtoset(cpc_t *cpc, cpc_event_t *event, int flags); /* * ce_cpuver values */ #define CPC_ULTRA1 1000 #define CPC_ULTRA2 1001 /* same as ultra1 for these purposes */ #define CPC_ULTRA3 1002 #define CPC_ULTRA3_PLUS 1003 #define CPC_ULTRA3_I 1004 #define CPC_ULTRA4_PLUS 1005 #define CPC_PENTIUM 2000 #define CPC_PENTIUM_MMX 2001 #define CPC_PENTIUM_PRO 2002 #define CPC_PENTIUM_PRO_MMX 2003 #define CPC_SPARC64_III 3000 #define CPC_SPARC64_V 3002 #endif /* __sparc || __i386 */ #if defined(__i386) || defined(__amd64) /* * This is common between i386 and amd64, because amd64 implements %tick. * Currently only used by the cpc tools to print the label atop the CPU ticks * column on amd64. */ #define CPC_TICKREG_NAME "tsc" #endif /* __i386 || __amd64 */ #if defined(__sparc) /* * UltraSPARC I, II, III and IV processors * * The performance counters on these processors allow up to two 32-bit * performance events to be captured simultaneously from a selection * of metrics. The metrics are selected by writing to the performance * control register, and subsequent values collected by reading from the * performance instrumentation counter registers. Both registers are * priviliged by default, and implemented as ASRs. */ struct _cpc_event { int ce_cpuver; hrtime_t ce_hrt; /* gethrtime() */ uint64_t ce_tick; /* virtualized %tick */ uint64_t ce_pic[2]; /* virtualized %pic */ uint64_t ce_pcr; /* %pcr */ }; #define CPC_TICKREG(ev) ((ev)->ce_tick) #define CPC_TICKREG_NAME "%tick" /* * "Well known" bitfields in the UltraSPARC %pcr register * The interfaces in libcpc should make these #defines uninteresting. */ #define CPC_ULTRA_PCR_USR 2 #define CPC_ULTRA_PCR_SYS 1 #define CPC_ULTRA_PCR_PRIVPIC 0 #define CPC_ULTRA_PCR_PIC0_SHIFT 4 #define CPC_ULTRA2_PCR_PIC0_MASK UINT64_C(0xf) #define CPC_ULTRA3_PCR_PIC0_MASK UINT64_C(0x3f) #define CPC_ULTRA_PCR_PIC1_SHIFT 11 #define CPC_ULTRA2_PCR_PIC1_MASK UINT64_C(0xf) #define CPC_ULTRA3_PCR_PIC1_MASK UINT64_C(0x3f) #elif defined(__i386) /* * Pentium I, II and III processors * * These CPUs allow pairs of events to captured. * The hardware counters count up to 40-bits of significance, but * only allow 32 (signed) bits to be programmed into them. * Pentium I and Pentium II processors are programmed differently, but * the resulting counters and timestamps can be handled portably. */ struct _cpc_event { int ce_cpuver; hrtime_t ce_hrt; /* gethrtime() */ uint64_t ce_tsc; /* virtualized rdtsc value */ uint64_t ce_pic[2]; /* virtualized PerfCtr[01] */ uint32_t ce_pes[2]; /* Pentium II */ #define ce_cesr ce_pes[0] /* Pentium I */ }; #define CPC_TICKREG(ev) ((ev)->ce_tsc) /* * "Well known" bit fields in the Pentium CES register * The interfaces in libcpc should make these #defines uninteresting. */ #define CPC_P5_CESR_ES0_SHIFT 0 #define CPC_P5_CESR_ES0_MASK 0x3f #define CPC_P5_CESR_ES1_SHIFT 16 #define CPC_P5_CESR_ES1_MASK 0x3f #define CPC_P5_CESR_OS0 6 #define CPC_P5_CESR_USR0 7 #define CPC_P5_CESR_CLK0 8 #define CPC_P5_CESR_PC0 9 #define CPC_P5_CESR_OS1 (CPC_P5_CESR_OS0 + 16) #define CPC_P5_CESR_USR1 (CPC_P5_CESR_USR0 + 16) #define CPC_P5_CESR_CLK1 (CPC_P5_CESR_CLK0 + 16) #define CPC_P5_CESR_PC1 (CPC_P5_CESR_PC0 + 16) /* * "Well known" bit fields in the Pentium Pro PerfEvtSel registers * The interfaces in libcpc should make these #defines uninteresting. */ #define CPC_P6_PES_INV 23 #define CPC_P6_PES_EN 22 #define CPC_P6_PES_INT 20 #define CPC_P6_PES_PC 19 #define CPC_P6_PES_E 18 #define CPC_P6_PES_OS 17 #define CPC_P6_PES_USR 16 #define CPC_P6_PES_UMASK_SHIFT 8 #define CPC_P6_PES_UMASK_MASK (0xffu) #define CPC_P6_PES_CMASK_SHIFT 24 #define CPC_P6_PES_CMASK_MASK (0xffu) #define CPC_P6_PES_PIC0_MASK (0xffu) #define CPC_P6_PES_PIC1_MASK (0xffu) #endif /* __i386 */ #ifdef __cplusplus } #endif #endif /* _LIBCPC_IMPL_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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION SUNW_1.3 { global: cpc_walk_generic_events_pic; cpc_walk_generic_events_all; } SUNW_1.2; SYMBOL_VERSION SUNW_1.2 { global: cpc_bind_cpu; cpc_bind_curlwp; cpc_bind_pctx; cpc_buf_add; cpc_buf_copy; cpc_buf_create; cpc_buf_destroy; cpc_buf_get; cpc_buf_hrtime; cpc_buf_set; cpc_buf_sub; cpc_buf_tick; cpc_buf_zero; cpc_caps; cpc_cciname; cpc_close; cpc_cpuref; cpc_disable; cpc_enable; cpc_npic; cpc_open; cpc_request_preset; cpc_set_add_request; cpc_set_create; cpc_set_destroy; cpc_seterrhndlr; cpc_set_restart; cpc_set_sample; cpc_unbind; cpc_walk_attrs; cpc_walk_events_all; cpc_walk_events_pic; cpc_walk_requests; # On all platforms other than amd64, SUNW_1.2 inherts SUNW_1.1, # which supplies additional functions. On amd64, there is no SUNW_1.1. $if !(_x86 && _ELF64) } SUNW_1.1 ; SYMBOL_VERSION SUNW_1.1 { global: cpc_access; cpc_bind_event; cpc_count_sys_events; cpc_count_usr_events; cpc_event_accum; cpc_event_diff; cpc_eventtostr; cpc_getcciname; cpc_getcpuref; cpc_getcpuver; cpc_getnpic; cpc_getusage; cpc_pctx_bind_event; cpc_pctx_invalidate; cpc_pctx_rele; cpc_pctx_take_sample; cpc_rele; cpc_seterrfn; cpc_shared_bind_event; cpc_shared_close; cpc_shared_open; cpc_shared_rele; cpc_shared_take_sample; cpc_strtoevent; cpc_take_sample; cpc_version; cpc_walk_names; $endif }; SYMBOL_VERSION SUNWprivate_1.1 { global: cpc_terminate; local: *; }; /* * 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 2005 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 #include "libcpc.h" #include "libcpc_impl.h" /* * CPC library handle for use by CPCv1 implementation. */ cpc_t *__cpc = NULL; mutex_t __cpc_lock; /* protects __cpc handle */ int __cpc_v1_cpuver; /* CPU version in use by CPCv1 client */ #ifdef __sparc uint64_t __cpc_v1_pcr; /* last bound %pcr value */ #else uint32_t __cpc_v1_pes[2]; /* last bound %pes values */ #endif /* __sparc */ int __cpc_init(void) { const char *fn = "__cpc_init"; extern cpc_t *__cpc; /* CPC handle for obsolete clients to share */ (void) mutex_lock(&__cpc_lock); if (__cpc == NULL && (__cpc = cpc_open(CPC_VER_CURRENT)) == NULL) { __cpc_error(fn, dgettext(TEXT_DOMAIN, "Couldn't open CPC library handle\n")); (void) mutex_unlock(&__cpc_lock); return (-1); } (void) mutex_unlock(&__cpc_lock); return (0); } int cpc_bind_event(cpc_event_t *this, int flags) { cpc_set_t *set; cpc_request_t *rp; int ret; if (this == NULL) { (void) cpc_rele(); return (0); } if (__cpc_init() != 0) { errno = ENXIO; return (-1); } /* * The cpuver and control fields of the cpc_event_t must be saved off * for later. The user may call cpc_take_sample(), expecting these to * be copied into a different cpc_event_t struct by the kernel. We have * to fake that behavior for CPCv1 clients. */ __cpc_v1_cpuver = this->ce_cpuver; #ifdef __sparc __cpc_v1_pcr = this->ce_pcr; #else __cpc_v1_pes[0] = this->ce_pes[0]; __cpc_v1_pes[1] = this->ce_pes[1]; #endif /* __sparc */ if ((set = __cpc_eventtoset(__cpc, this, flags)) == NULL) { errno = EINVAL; return (-1); } /* * Convert flags to CPC2. */ if (flags & CPC_BIND_EMT_OVF) { for (rp = set->cs_request; rp != NULL; rp = rp->cr_next) rp->cr_flags |= CPC_OVF_NOTIFY_EMT; flags &= ~CPC_BIND_EMT_OVF; } ret = cpc_bind_curlwp(__cpc, set, flags); (void) cpc_set_destroy(__cpc, set); return (ret); } int cpc_take_sample(cpc_event_t *this) { this->ce_cpuver = __cpc_v1_cpuver; #ifdef __sparc this->ce_pcr = __cpc_v1_pcr; #else this->ce_pes[0] = __cpc_v1_pes[0]; this->ce_pes[1] = __cpc_v1_pes[1]; #endif /* __sparc */ return (syscall(SYS_cpc, CPC_SAMPLE, -1, this->ce_pic, &this->ce_hrt, &CPC_TICKREG(this), 0)); } int cpc_count_usr_events(int enable) { return (syscall(SYS_cpc, CPC_USR_EVENTS, -1, enable, 0)); } int cpc_count_sys_events(int enable) { return (syscall(SYS_cpc, CPC_SYS_EVENTS, -1, enable, 0)); } int cpc_rele(void) { return (syscall(SYS_cpc, CPC_RELE, -1, NULL, 0)); } /* * See if the system call is working and installed. * * We invoke the system call with nonsense arguments - if it's * there and working correctly, it will return EINVAL. * * (This avoids the user getting a SIGSYS core dump when they attempt * to bind on older hardware) */ int cpc_access(void) { void (*handler)(int); int error = 0; const char fn[] = "access"; handler = signal(SIGSYS, SIG_IGN); if (syscall(SYS_cpc, -1, -1, NULL, 0) == -1 && errno != EINVAL) error = errno; (void) signal(SIGSYS, handler); switch (error) { case EAGAIN: __cpc_error(fn, dgettext(TEXT_DOMAIN, "Another process may be " "sampling system-wide CPU statistics\n")); break; case ENOSYS: __cpc_error(fn, dgettext(TEXT_DOMAIN, "CPU performance counters " "are inaccessible on this machine\n")); break; default: __cpc_error(fn, "%s\n", strerror(errno)); break; case 0: return (0); } errno = error; return (-1); } /* * To look at the system-wide counters, we have to open the * 'shared' device. Once that device is open, no further contexts * can be installed (though one open is needed per CPU) */ int cpc_shared_open(void) { const char driver[] = CPUDRV_SHARED; return (open(driver, O_RDWR)); } void cpc_shared_close(int fd) { (void) cpc_shared_rele(fd); (void) close(fd); } int cpc_shared_bind_event(int fd, cpc_event_t *this, int flags) { extern cpc_t *__cpc; cpc_set_t *set; int ret; char *packed_set; size_t packsize; int subcode; __cpc_args_t cpc_args; if (this == NULL) { (void) cpc_shared_rele(fd); return (0); } else if (flags != 0) { errno = EINVAL; return (-1); } if (__cpc_init() != 0) { errno = ENXIO; return (-1); } if ((set = __cpc_eventtoset(__cpc, this, flags)) == NULL) { errno = EINVAL; return (-1); } __cpc_v1_cpuver = this->ce_cpuver; if ((packed_set = __cpc_pack_set(set, flags, &packsize)) == NULL) { errno = ENOMEM; return (-1); } cpc_args.udata1 = packed_set; cpc_args.udata2 = (void *)packsize; cpc_args.udata3 = (void *)&subcode; ret = ioctl(fd, CPCIO_BIND, &cpc_args); free(packed_set); (void) cpc_set_destroy(__cpc, set); return (ret); } int cpc_shared_take_sample(int fd, cpc_event_t *this) { __cpc_args_t args; args.udata1 = this->ce_pic; args.udata2 = &this->ce_hrt; args.udata3 = &CPC_TICKREG(this); this->ce_cpuver = __cpc_v1_cpuver; return (ioctl(fd, CPCIO_SAMPLE, &args)); } int cpc_shared_rele(int fd) { return (ioctl(fd, CPCIO_RELE, 0)); } int cpc_pctx_bind_event(pctx_t *pctx, id_t lwpid, cpc_event_t *event, int flags) { cpc_set_t *set; int ret; if (event == NULL) return (cpc_pctx_rele(pctx, lwpid)); if (__cpc_init() != 0) { errno = ENXIO; return (-1); } else if (flags != 0) { errno = EINVAL; return (-1); } if ((set = __cpc_eventtoset(__cpc, event, flags)) == NULL) { errno = EINVAL; return (-1); } /* * The cpuver and control fields of the cpc_event_t must be saved off * for later. The user may call cpc_take_sample(), expecting these to * be copied into a different cpc_event_t struct by the kernel. We have * to fake that behavior for CPCv1 clients. */ __cpc_v1_cpuver = event->ce_cpuver; ret = cpc_bind_pctx(__cpc, pctx, lwpid, set, 0); (void) cpc_set_destroy(__cpc, set); return (ret); } int cpc_pctx_take_sample(pctx_t *pctx, id_t lwpid, cpc_event_t *event) { event->ce_cpuver = __cpc_v1_cpuver; return (__pctx_cpc(pctx, __cpc, CPC_SAMPLE, lwpid, event->ce_pic, &event->ce_hrt, &CPC_TICKREG(event), CPC1_BUFSIZE)); } /* * Given a process context and an lwpid, mark the CPU performance * counter context as invalid. */ int cpc_pctx_invalidate(pctx_t *pctx, id_t lwpid) { return (__pctx_cpc(pctx, __cpc, CPC_INVALIDATE, lwpid, 0, 0, 0, 0)); } /* * Given a process context and an lwpid, remove all our * hardware context from it. */ int cpc_pctx_rele(pctx_t *pctx, id_t lwpid) { return (__pctx_cpc(pctx, __cpc, CPC_RELE, lwpid, 0, 0, 0, 0)); } static cpc_errfn_t *__cpc_uerrfn; /*PRINTFLIKE2*/ void __cpc_error(const char *fn, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (__cpc_uerrfn) __cpc_uerrfn(fn, fmt, ap); else { (void) fprintf(stderr, "libcpc: %s: ", fn); (void) vfprintf(stderr, fmt, ap); } va_end(ap); } void cpc_seterrfn(cpc_errfn_t *errfn) { __cpc_uerrfn = errfn; } /* * cpc_version() is only for CPC1 clients. */ uint_t __cpc_workver = CPC_VER_1; uint_t cpc_version(uint_t ver) { __cpc_workver = CPC_VER_1; switch (ver) { case CPC_VER_NONE: case CPC_VER_CURRENT: return (CPC_VER_CURRENT); case CPC_VER_1: /* * As long as the client is using cpc_version() at all, it is * a CPCv1 client. We still allow CPCv1 clients to compile on * CPCv2 systems. */ return (CPC_VER_1); } return (CPC_VER_NONE); } /* * 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 #include #include #include #include "libcpc.h" #include "libcpc_impl.h" /* * Pack a request set into a buffer using libnvpair. Size of buffer is returned * in buflen. */ char * __cpc_pack_set(cpc_set_t *set, uint_t flags, size_t *buflen) { cpc_request_t *req; nvlist_t *setlist, **reqlist; size_t packsize = 0; char *buf = NULL; int i; int j; if (nvlist_alloc(&setlist, 0, 0) == ENOMEM) { errno = ENOMEM; return (NULL); } if ((reqlist = (nvlist_t **)malloc(set->cs_nreqs * sizeof (*reqlist))) == NULL) { nvlist_free(setlist); errno = ENOMEM; return (NULL); } bzero((void *)reqlist, set->cs_nreqs * sizeof (*reqlist)); i = 0; for (req = set->cs_request; req != NULL; req = req->cr_next) { if (nvlist_alloc(&reqlist[i], 0, 0) == ENOMEM) goto nomem; if (nvlist_add_string(reqlist[i], "cr_event", req->cr_event) != 0) goto nomem; if (nvlist_add_uint64(reqlist[i], "cr_preset", req->cr_preset) != 0) goto nomem; if (nvlist_add_uint32(reqlist[i], "cr_flags", req->cr_flags) != 0) goto nomem; if (nvlist_add_uint32(reqlist[i], "cr_index", req->cr_index) != 0) goto nomem; if (req->cr_nattrs != 0) { nvlist_t *attrs; if (nvlist_alloc(&attrs, NV_UNIQUE_NAME, 0) == ENOMEM) goto nomem; for (j = 0; j < req->cr_nattrs; j++) { if (nvlist_add_uint64(attrs, req->cr_attr[j].ka_name, req->cr_attr[j].ka_val) != 0) { nvlist_free(attrs); goto nomem; } } if (nvlist_add_nvlist(reqlist[i], "cr_attr", attrs) != 0) { nvlist_free(attrs); goto nomem; } nvlist_free(attrs); } i++; } if (nvlist_add_nvlist_array(setlist, "reqs", reqlist, set->cs_nreqs) != 0) goto nomem; if (nvlist_add_uint32(setlist, "flags", flags) != 0) goto nomem; if (nvlist_pack(setlist, &buf, &packsize, NV_ENCODE_NATIVE, 0) != 0) goto nomem; for (i = 0; i < set->cs_nreqs; i++) nvlist_free(reqlist[i]); nvlist_free(setlist); free(reqlist); *buflen = packsize; return (buf); nomem: for (i = 0; i < set->cs_nreqs; i++) { if (reqlist[i] != 0) nvlist_free(reqlist[i]); } nvlist_free(setlist); free(reqlist); errno = ENOMEM; return (NULL); } cpc_strhash_t * __cpc_strhash_alloc(void) { cpc_strhash_t *p; if ((p = malloc(sizeof (cpc_strhash_t))) == NULL) return (NULL); p->str = ""; p->cur = NULL; p->next = NULL; return (p); } void __cpc_strhash_free(cpc_strhash_t *hash) { cpc_strhash_t *p = hash, *f; while (p != NULL) { f = p; p = p->next; free(f); } } /* * Insert a new key into the hash table. * * Returns 0 if key was unique and insert successful. * * Returns 1 if key was already in table and no insert took place. * * Returns -1 if out of memory. */ int __cpc_strhash_add(cpc_strhash_t *hash, char *key) { cpc_strhash_t *p, *tmp; for (p = hash; p != NULL; p = p->next) { if (strcmp(p->str, key) == 0) return (1); } if ((p = malloc(sizeof (*p))) == NULL) return (-1); p->str = key; tmp = hash->next; hash->next = p; p->next = tmp; /* * The head node's current pointer must stay pointed at the first * real node. We just inserted at the head. */ hash->cur = p; return (0); } char * __cpc_strhash_next(cpc_strhash_t *hash) { cpc_strhash_t *p; if (hash->cur != NULL) { p = hash->cur; hash->cur = hash->cur->next; return (p->str); } return (NULL); }