dtj_consume.c revision b8fac8e162eda7e98db13dfa3e439e43f90f41d9
/*
* 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
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <limits.h>
#include <signal.h>
#include <libproc.h>
#include <pthread.h>
#include <dtrace_jni.h>
/*
* Implements the work done in the running consumer loop. The native Java
* methods (JNI layer) are implemented in dtrace_jni.c.
*/
/* Record handler passed to dtrace_work() */
void *);
/* Probe data handler passed to dtrace_work() */
static int dtj_chew(const dtrace_probedata_t *, void *);
/* Processes requests from LocalConsumer enqueued during dtrace_sleep() */
/*
* Callback handlers set in dtj_set_callback_handlers(), called from libdtrace
* in the consumer loop (from dtrace_work())
*/
static int dtj_drophandler(const dtrace_dropdata_t *, void *);
static int dtj_errhandler(const dtrace_errdata_t *, void *);
static void dtj_prochandler(struct ps_prochandle *, const char *, void *);
static int dtj_setopthandler(const dtrace_setoptdata_t *, void *);
/*
* Buffered output handler called from libdtrace in both the consumer loop (from
* dtrace_work()) and the get_aggregate() function (from
* dtrace_aggregate_print()).
*/
static int dtj_bufhandler(const dtrace_bufdata_t *, void *);
/* Conversion of libdtrace data into Java Objects */
const dtrace_recdesc_t *, dtj_java_consumer_t *);
const dtrace_recdesc_t *, dtj_java_consumer_t *);
/* Aggregation data */
const dtrace_recdesc_t *, const char *, dtj_java_consumer_t *);
const dtrace_recdesc_t *, const char *, dtj_java_consumer_t *);
const dtrace_recdesc_t *, dtj_java_consumer_t *);
const dtrace_recdesc_t *);
/* Aggregation functions */
static void dtj_aggwalk_init(dtj_java_consumer_t *);
static int dtj_clear(const dtrace_aggdata_t *, void *);
/*
* The consumer loop needs to protect calls to libdtrace functions with a global
* lock. JNI native method calls in dtrace_jni.c are already protected and do
* not need this function.
*/
{
/* Must not call MonitorEnter with a pending exception */
return (DTJ_ERR);
}
/* Grab global lock */
return (DTJ_ERR);
}
return (DTJ_ERR);
}
return (DTJ_OK);
}
/*
* Protected by global lock (LocalConsumer.class) that protects call to
* Java_org_opensolaris_os_dtrace_LocalConsumer__1go()
*/
{
"failed to establish buffered handler: %s",
return (DTJ_ERR);
}
"failed to establish drop handler: %s",
return (DTJ_ERR);
}
"failed to establish error handler: %s",
return (DTJ_ERR);
}
"failed to establish proc handler: %s",
return (DTJ_ERR);
}
"couldn't get option %s: %s", "flowindent",
return (DTJ_ERR);
}
"failed to establish setopt handler: %s",
return (DTJ_ERR);
}
return (DTJ_OK);
}
static int
/* ARGSUSED */
{
const char *dropkind;
return (DTRACE_HANDLE_ABORT);
}
switch (data->dtdda_kind) {
case DTRACEDROP_PRINCIPAL:
dropkind = "PRINCIPAL";
break;
case DTRACEDROP_AGGREGATION:
dropkind = "AGGREGATION";
break;
case DTRACEDROP_DYNAMIC:
dropkind = "DYNAMIC";
break;
case DTRACEDROP_DYNRINSE:
dropkind = "DYNRINSE";
break;
case DTRACEDROP_DYNDIRTY:
dropkind = "DYNDIRTY";
break;
case DTRACEDROP_SPEC:
dropkind = "SPEC";
break;
case DTRACEDROP_SPECBUSY:
dropkind = "SPECBUSY";
break;
case DTRACEDROP_SPECUNAVAIL:
dropkind = "SPECUNAVAIL";
break;
dropkind = "STKSTROVERFLOW";
break;
case DTRACEDROP_DBLERROR:
dropkind = "DBLERROR";
break;
default:
dropkind = "UNKNOWN";
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_OK);
}
static int
/* ARGSUSED */
{
const char *f;
if (!probe) {
return (DTRACE_HANDLE_ABORT);
}
if (f) {
return (DTRACE_HANDLE_ABORT);
}
}
switch (data->dteda_fault) {
case DTRACEFLT_BADADDR:
case DTRACEFLT_BADALIGN:
case DTRACEFLT_BADSTACK:
break;
default:
addr = -1;
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_OK);
}
/*
* Since the function signature does not allow us to return an abort signal, we
* need to temporarily clear any pending exception before returning, since
* without the abort we can't guarantee that the exception will be checked in
* time to prevent invalid JNI function calls.
*/
static void
/* ARGSUSED */
{
int signal = -1;
char signame[SIG2STR_MAX];
const char *statusname;
switch (Pstate(P)) {
case PS_RUN:
statusname = "RUN";
break;
case PS_STOP:
statusname = "STOP";
break;
case PS_UNDEAD:
statusname = "UNDEAD";
}
goto proc_end;
}
}
break;
case PS_LOST:
statusname = "LOST";
break;
case PS_DEAD:
/*
* PS_DEAD not handled by dtrace.c prochandler, still this is a
* case of process termination and it can't hurt to handle it.
*/
statusname = "DEAD";
break;
default:
/*
* Unexpected, but erring on the side of tolerance by not
* crashing the consumer. Failure to notify listeners of
* process state not handled by the dtrace.c prochandler does
* not seem serious.
*/
return;
}
goto proc_end;
}
if (msg) {
if (!message) {
goto proc_end;
}
}
goto proc_end;
}
/* valid exit status */
goto proc_end;
}
}
/*
* Save the exception so we can rethrow it later when it's safe.
*/
if (!jc->dtjj_exception) {
jc->dtjj_exception = e;
}
}
}
static int
/* ARGSUSED */
{
}
return (DTRACE_HANDLE_OK);
}
/*
* Most of this function lifted from libdtrace/common/dt_consume.c
* dt_print_bytes().
*/
static jobject
{
/*
* If the byte stream is a series of printable characters, followed by
* a terminating byte, we print it out as a string. Otherwise, we
* assume that it's something else and just print the bytes.
*/
int i, j;
char *c = addr;
if (nbytes == 0) {
}
for (i = 0; i < nbytes; i++) {
/*
* We define a "printable character" to be one for which
* isprint(3C) returns non-zero, isspace(3C) returns non-zero,
* or a character which is either backspace or the bell.
* Backspace and the bell are regrettably special because
* they fail the first two tests -- and yet they are entirely
* printable. These are the only two control characters that
* have meaning for the terminal and for which isprint(3C) and
* isspace(3C) return 0.
*/
c[i] == '\b' || c[i] == '\a')
continue;
if (c[i] == '\0' && i > 0) {
/*
* This looks like it might be a string. Before we
* assume that it is indeed a string, check the
* remainder of the byte range; if it contains
* additional non-nul characters, we'll assume that
* it's a binary stream that just happens to look like
* a string.
*/
for (j = i + 1; j < nbytes; j++) {
if (c[j] != '\0')
break;
}
if (j != nbytes)
break;
/* It's a string */
}
break;
}
if (i == nbytes) {
/*
* The byte range is all printable characters, but there is
* no trailing nul byte. We'll assume that it's a string.
*/
if (!s) {
"failed to allocate string value");
return (NULL);
}
s[nbytes] = '\0';
free(s);
return (jobj);
}
/* return byte array */
return (NULL);
}
(const jbyte *)c);
return (NULL);
}
return (jobj);
}
/*
* Return NULL if memory could not be allocated (OutOfMemoryError is thrown in
* that case).
*/
static jobject
{
switch (size) {
case 1:
break;
case 2:
/* LINTED - alignment */
break;
case 4:
/* LINTED - alignment */
break;
case 8:
/* LINTED - alignment */
break;
default:
break;
}
if (!jobj) {
return (NULL); /* OutOfMemoryError pending */
}
return (jrec);
}
/*
* This is the record handling function passed to dtrace_work(). It differs
* from the bufhandler registered with dtrace_handle_buffered() as follows:
*
* 1. It does not have access to libdtrace formatted output.
* 2. It is called once for every D program statement, not for every
* output-producing D action or aggregation record. A statement may be a
* variable assignment, having no size and producing no output.
* 3. It is called for the D exit() action; the bufhandler is not.
* 4. In response to the printa() action, it is called with a record having an
* action of type DTRACEACT_PRINTA. The bufhandler never sees that action
* value. It only sees the output-producing aggregation records.
* 5. It is called with a NULL record at the end of each probedata.
*/
static int
void *arg)
{
int r;
/*
* Update the record index to that of the current record, or to that of
* the last record if rec is NULL (signalling end of probe data).
*/
} else {
/*
* This record handler is called once for the printf() action,
* but there may be multiple records in the probedata
* corresponding to the unformatted elements of that printf().
* We don't know ahead of time how many probedata records
* libdtrace will consume to produce output for one printf()
* action, so we look back at the previous call to dtj_chewrec()
* to see how many probedata records were consumed. All
* non-null elements in the range from the previous record index
* up to and not including the current record index are assumed
* to be unformatted printf() elements, and will be attached to
* the PrintfRecord from the previous call. A null element in
* that range is the result of a D program statement preceding
* the printf() that is not a D action. These generate
* probedata records accounted for by the null placeholder, but
* do not advance the probedata offset and are not part of the
* subsequent printf().
*
* If rec->dtrd_size == 0, the record represents a D program
* statement that is not a D action. It has no size and does
* not advance the offset in the probedata. Handle it normally
* without special-casing or premature return, since in all
* cases we look at the previous record later in this function.
*/
((r < edesc->dtepd_nrecs) &&
++r) {
}
}
/*
* Attach the Java representations of the libdtrace data elements
* pertaining to the previous call to this record handler to the
* previous Java Record. (All data elements belonging to the current
* probedata are added to a single list by the probedata consumer
* function dtj_chew() before this record consumer function is ever
* called.) For example, if the previous Record was generated by the
* printf() action, and dtj_chew() listed 3 records for its 3
* unformatted elements, those 3 libdtrace records comprise 1
* PrintfRecord. Note that we cannot know how many data elements apply
* to the current rec until we find out the data index where the next
* rec starts. (The knowledge of how many probedata records to consume
* is private to libdtrace.)
*/
return (DTRACE_CONSUME_ABORT);
}
}
/*
* End of probe data. Notify listeners of the new ProbeData
* instance.
*/
if (jc->dtjj_probedata) {
/* previous probedata */
return (DTRACE_CONSUME_ABORT);
}
/*
* Do not wrap exception thrown from
* ConsumerListener.
*/
return (DTRACE_CONSUME_ABORT);
}
}
return (DTRACE_CONSUME_NEXT);
}
/* Set previous record action and data index to current */
switch (act) {
case DTRACEACT_DIFEXPR:
/*
* The current record is not a D action, but a program
* statement such as a variable assignment, not to be
* confused with the trace() action.
*/
break;
}
/*
* Add a Record for the trace() action that references the
* native probedata element listed at the current index.
*/
return (DTRACE_CONSUME_ABORT);
}
break;
case DTRACEACT_PRINTF:
/*
* Just add an empty PrintfRecord for now. We'll attach the
* unformatted elements in a subsequent call to this function.
* (We don't know how many there will be.)
*/
return (DTRACE_CONSUME_ABORT);
}
/* defer formatted string to dtj_bufhandler() */
break;
case DTRACEACT_PRINTA: {
return (DTRACE_CONSUME_ABORT);
}
(rec->dtrd_format != 0));
return (DTRACE_CONSUME_ABORT);
}
/*
* Create a StringBuilder to collect the pieces of
* formatted output into a single String.
*/
if (!jbuf) {
/* OutOfMemoryError pending */
return (DTRACE_CONSUME_ABORT);
}
}
/* defer aggregation records to dtj_bufhandler() */
break;
}
case DTRACEACT_EXIT:
/*
* Add a Record for the exit() action that references the native
* probedata element listed at the current index.
*/
return (DTRACE_CONSUME_ABORT);
}
return (DTRACE_CONSUME_NEXT);
}
return (DTRACE_CONSUME_THIS);
}
/*
* This is the probe handling function passed to dtrace_work(). It is is called
* once every time a probe fires. It is the first of all the callbacks for the
* current probe. It is followed by multiple callbacks to dtj_chewrec(), one
* for each probedata record. Each call to dtj_chewrec() is followed by zero or
* more callbacks to the bufhandler, one for each output-producing action or
* aggregation record.
*/
static int
{
int epid;
int cpu;
int nrecs;
int i;
/* java exception pending */
return (DTRACE_CONSUME_ABORT);
}
const char *kind;
switch (data->dtpda_flow) {
case DTRACEFLOW_ENTRY:
kind = "ENTRY";
break;
case DTRACEFLOW_RETURN:
kind = "RETURN";
break;
case DTRACEFLOW_NONE:
kind = "NONE";
break;
default:
}
int depth;
return (DTRACE_CONSUME_ABORT);
}
/*
* Use the knowledge that libdtrace indents 2 spaces per
* level in the call stack to calculate the depth.
*/
return (DTRACE_CONSUME_ABORT);
}
}
}
/* Create ProbeData instance */
return (DTRACE_CONSUME_ABORT);
}
/*
* Populate the ProbeData list of Java data elements in advance so we
* don't need to peek back in the record handler at libdtrace records
* that have already been consumed. In the Java API, each ProbeData
* Record is generated by one D action, while in the native libdtrace
* there may be more than one probedata record (each a single data
* element) per D action. For example PrintfRecord has multiple
* unformatted elements, each represented by a native probedata record,
* but combined by the API into a single PrintfRecord.
*/
for (i = 0; i < nrecs; ++i) {
/*
* A statement that is not a D action, such as assignment to a
* variable, has no size. Add a NULL placeholder to the scratch
* list of Java probedata elements in that case.
*/
} else {
}
return (DTRACE_CONSUME_ABORT);
}
}
return (DTRACE_CONSUME_ABORT);
}
}
return (DTRACE_CONSUME_ABORT);
}
/* Initialize per-consumer probedata fields */
return (DTRACE_CONSUME_ABORT);
}
return (DTRACE_CONSUME_THIS);
}
/*
* This is the buffered output handler registered with dtrace_handle_buffered().
* It's purpose is to make the output of the libdtrace print routines available
* to this API, without writing any of it to a file (such as stdout). This is
* needed for the stack(), ustack(), and jstack() actions to get human-readable
* stack values, since there is no public function in libdtrace to convert stack
* values to strings. It is also used to get the formatted output of the D
* printf() and printa() actions.
*
* The bufhandler is called once for each output-producing, non-aggregating D
* action, such as trace() or printf(), and once for each libdtrace aggregation
* record (whether in response to the D printa() action, or the Consumer
* getAggregate() method). In the simple printa() case that takes one
* aggregation and does not specify a format string, there is one libdtrace
* record per tuple element plus one for the corresponding value. The complete
* When multiple aggregations are passed to printa(), each tuple is associated
* with a list of values, one from each aggregation. If a printa() format
* string does not specify placeholders for every aggregation value and tuple
* member, callbacks for those values and tuple members are omitted (and the
* data is omitted from the resulting PrintaRecord).
*
* Notes to characterize some non-obvious bufhandler behavior:
*
* 1. dtj_bufhandler() is never called with bufdata->dtbda_recdesc->dtrd_action
* DTRACEACT_PRINTA. That action only appears in the probedata consumer
* functions dtj_chew() and dtj_chewrec() before the bufhandler is called with
* subsequent aggregation records.
*
* 2. If printa() specifies a format string argument, then the bufhandler is
* the format string. If a stack() tuple member is omitted from the format
* string, its human-readable representation will not be available to this API,
* so the stack frame array is also omitted from the resulting
* AggregationRecord. The bufhandler is also called once for each string of
* characters surrounding printa() format string placeholders. For example,
* " %@d %d stack%k\n" results in the following callbacks:
* - two spaces
* - the aggregation value
* - a single space
* - the first tuple member (an integer)
* - " stack"
* - the second tuple member (a stack)
* - a newline
* A NULL record (NULL dtbda_recdesc) distinguishes a callback with interstitial
* format string characters from a callback with a tuple member or aggregation
* value (which has a non-NULL recdesc). The contents are also distinguished by
* the following flags:
* DTRACE_BUFDATA_AGGKEY
* DTRACE_BUFDATA_AGGVAL
* DTRACE_BUFDATA_AGGFORMAT
* DTRACE_BUFDATA_AGGLAST
*
* There is no final callback with the complete formatted string, so that must
* be concatenated across multiple callbacks to the bufhandler.
*
* 3. bufdata->dtbda_probe->dtpda_data may be overwritten by libdtrace print
* routines. The address is cached in the dtj_chew() function in case it is
* needed in the bufhandler.
*/
static int
/* ARGSUSED */
{
const dtrace_recdesc_t *rec;
const char *s;
/*
* Get the thread-specific java consumer. The bufhandler needs access
* to the correct JNI state specific to either the consumer loop or the
* getAggregate() call (aggregation snapshots can be requested
* asynchronously while the consumer loop generates PrintaRecords in
* dtrace_work() for ConsumerListeners).
*/
/*
* In at least one corner case (printa with multiple aggregations and a
* format string that does not completely specify the tuple), returning
* DTRACE_HANDLE_ABORT does not prevent a subsequent callback to this
* bufhandler. This check ensures that the invalid call is ignored.
*/
return (DTRACE_HANDLE_ABORT);
}
if (bufdata->dtbda_aggdata) {
}
s = bufdata->dtbda_buffered;
if (s == NULL) {
return (DTRACE_HANDLE_OK);
}
if (rec) {
}
switch (act) {
case DTRACEACT_DIFEXPR:
/* trace() action */
break;
case DTRACEACT_PRINTF:
/*
* Only the formatted string was not available to dtj_chewrec(),
* so we attach that now.
*/
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
break;
case DTRACEACT_STACK:
case DTRACEACT_USTACK:
case DTRACEACT_JSTACK:
/* stand-alone stack(), ustack(), or jstack() action */
if (!jstr) {
/* OutOfMemoryError pending */
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
break;
case DTRACEACT_USYM:
case DTRACEACT_UADDR:
case DTRACEACT_UMOD:
case DTRACEACT_SYM:
case DTRACEACT_MOD:
/* stand-alone symbol lookup action */
if (!jstr) {
/* OutOfMemoryError pending */
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
break;
default:
/*
* The record handler dtj_chewrec() defers nothing else to this
* bufhandler.
*/
break;
}
return (DTRACE_HANDLE_OK);
}
static boolean_t
{
switch (act) {
case DTRACEACT_STACK:
case DTRACEACT_USTACK:
case DTRACEACT_JSTACK:
break;
default:
}
return (stack_action);
}
static boolean_t
{
switch (act) {
case DTRACEACT_USYM:
case DTRACEACT_UADDR:
case DTRACEACT_UMOD:
case DTRACEACT_SYM:
case DTRACEACT_MOD:
break;
default:
}
return (symbol_action);
}
/*
* Called by get_aggregate() to clear only those aggregations specified by the
* caller.
*/
static int
{
if (jc->dtjj_aggregate_spec) {
if (!jname) {
/* java exception pending */
return (DTRACE_AGGWALK_ABORT);
}
return (DTRACE_AGGWALK_ABORT);
}
}
}
static int64_t
{
/* LINTED - alignment */
return (data[0] ?
}
static int64_t
{
/* LINTED - alignment */
}
static int64_t
{
/* LINTED - alignment */
return ((long long)data[0]);
}
static jobject
{
/* Get raw stack data */
}
static jobject
{
/* Get raw stack data */
if (!jobj) {
return (NULL); /* java exception pending */
}
return (NULL);
}
return (NULL);
}
return (NULL);
}
return (jobj);
}
static jobject
{
}
static jobject
{
if (!jobj) {
return (NULL); /* java exception pending */
}
/* Get symbol lookup */
if (!jstr) {
/* OutOfMemoryError pending */
return (NULL);
}
/* Trim leading and trailing whitespace */
/* trim() returns a new string; don't leak the old one */
return (NULL);
}
return (jobj);
}
/* Caller must be holding per-consumer lock */
static void
{
/* assert without crashing */
"stale aggregation tuple");
}
}
static jobject
{
int size; /* size of raw bytes not including trailing zeros */
int i; /* index of last non-zero byte */
/* trim trailing zeros */
}
size = (i + 1);
if (!raw) {
return (NULL); /* OutOfMemoryError pending */
}
return (NULL);
}
/* Create StackValueRecord instance from raw stack data */
switch (act) {
case DTRACEACT_STACK:
break;
case DTRACEACT_USTACK:
case DTRACEACT_JSTACK:
/* Get pid of user process */
break;
default:
"Expected stack action, got %d\n", act);
}
return (NULL);
}
return (stack);
}
static jobject
{
switch (act) {
case DTRACEACT_SYM:
case DTRACEACT_MOD:
/* LINTED - alignment */
g_symbolinit_jm, *pc);
break;
case DTRACEACT_USYM:
case DTRACEACT_UADDR:
case DTRACEACT_UMOD:
/* Get pid of user process */
++pc;
break;
default:
"Expected stack action, got %d\n", act);
}
return (NULL);
}
return (symbol);
}
/*
* Return NULL if java exception pending, otherwise return Distribution value.
*/
static jobject
{
/* LINTED - alignment */
int levels;
int n; /* number of buckets */
/* distribution */
if (act == DTRACEAGG_LQUANTIZE) {
/* first "bucket" used for range and step */
value = *aggbuckets++;
/*
* Add one for the base bucket and one for the bucket of values
* less than the base.
*/
n = levels + 2;
} else {
}
"size mismatch: record %d, buckets %d", size,
(n * sizeof (uint64_t)));
return (NULL);
}
if (!jbuckets) {
return (NULL); /* exception pending */
}
if (n > 0) {
/* check for ArrayIndexOutOfBounds */
return (NULL);
}
}
if (act == DTRACEAGG_LQUANTIZE) {
/* Must pass 64-bit base and step or constructor gets junk. */
} else {
jbuckets);
}
if (!jdist) {
return (NULL); /* exception pending */
}
if (normal != 1) {
return (NULL);
}
}
return (jdist);
}
static void
{
frames);
frames);
}
}
static void
{
}
}
/*
* Note: It is not valid to look outside the current libdtrace record in the
* given aggdata (except to get the aggregation ID from the first record).
*
* Return DTRACE_HANDLE_ABORT if java exception pending, otherwise
* DTRACE_HANDLE_OK.
*/
static int
{
const dtrace_aggdesc_t *aggdesc;
const char *s = bufdata->dtbda_buffered;
/* Assert without crashing */
return (DTRACE_HANDLE_ABORT);
}
/*
* Get the aggregation ID from the first record.
*/
/* LINTED - alignment */
if (aggid < 0) {
/* Assert without crashing */
return (DTRACE_HANDLE_ABORT);
}
/* Append buffered output if this is a printa() callback. */
return (DTRACE_HANDLE_ABORT);
}
/*
* StringBuilder append() returns a reference to the
* StringBuilder; must not leak the returned reference.
*/
return (DTRACE_HANDLE_ABORT);
}
} else {
/*
* Test whether to include the aggregation if this is a
* getAggregate() callback. Optimization: perform the inclusion
* test only when the aggregation has changed.
*/
return (DTRACE_HANDLE_ABORT);
}
}
return (DTRACE_HANDLE_OK);
}
}
/*
* Determine the expected number of tuple members. While it is not
* technically valid to look outside the current record in the current
* aggdata, this implementation does so without a known failure case.
* Any method relying only on the current callback record makes riskier
* assumptions and still does not cover every corner case (for example,
* counting the records from index 1 up to and not including the index
* of the current DTRACE_BUFDATA_AGGVAL record, which fails when a
* format string specifies the value ahead of one or more tuple
* elements). Knowing that the calculation of the expected tuple size
* is technically invalid (because it looks outside the current record),
* we make the calculation at the earliest opportunity, before anything
* might happen to invalidate any part of the aggdata. It ought to be
* safe in any case: dtrd_action and dtrd_size do not appear ever to be
* overwritten, and dtrd_offset is not used outside the current record.
*
* It is possible (if the assumptions here ever prove untrue) that the
* libdtrace buffered output handler may need to be enhanced to provide
* the expected number of tuple members.
*/
int r;
if (DTRACEACT_ISAGG(act) ||
break;
}
}
}
/* record value is a tuple member */
if (!jc->dtjj_tuple) {
/* java exception pending */
return (DTRACE_HANDLE_ABORT);
}
}
switch (act) {
case DTRACEACT_STACK:
case DTRACEACT_USTACK:
case DTRACEACT_JSTACK:
break;
case DTRACEACT_USYM:
case DTRACEACT_UADDR:
case DTRACEACT_UMOD:
case DTRACEACT_SYM:
case DTRACEACT_MOD:
break;
default:
}
if (!jobj) {
/* java exception pending */
return (DTRACE_HANDLE_ABORT);
}
return (DTRACE_HANDLE_ABORT);
}
/*
* Record value is that of an aggregating action. The printa()
* format string may place the tuple ahead of the aggregation
* value(s), so we can't be sure we have the tuple until we get
* the AGGLAST flag indicating the last callback associated with
* the current tuple. Save the aggregation value or values
* (multiple values if more than one aggregation is passed to
* printa()) until then.
*/
if (!jvalue) {
/* java exception pending */
return (DTRACE_HANDLE_ABORT);
}
aggid);
if (!aggval) {
/* OutOfMemoryError pending */
return (DTRACE_HANDLE_ABORT);
}
/* deletes jvalue reference */
return (DTRACE_HANDLE_ABORT);
}
}
/* No more values associated with the current tuple. */
int tuple_member_count;
/*
* singleton aggregation declared in D with no square
* brackets
*/
"Failed to reference Tuple.EMPTY");
return (DTRACE_HANDLE_ABORT);
}
}
goto printa_output;
}
if (tuple_member_count <
goto printa_output;
}
/*
* new AggregationRecord: Combine the aggregation value
* with the saved tuple and add it to the current
* Aggregate or PrintaRecord.
*/
aggval->dtja_value);
if (!jrec) {
/* java exception pending */
return (DTRACE_HANDLE_ABORT);
}
/* aggregation name */
if (!jname) {
/* OutOfMemoryError pending */
return (DTRACE_HANDLE_ABORT);
}
/*
* If the printa() format string specifies the value of
* the aggregating action multiple times, PrintaRecord
* ignores the attempt to add the duplicate record.
*/
/* add to PrintaRecord */
} else {
/* add to Aggregate */
}
return (DTRACE_HANDLE_ABORT);
}
}
jenv);
/*
* Get the formatted string associated with the current
* tuple if this is a printa() callback.
*/
return (DTRACE_HANDLE_ABORT);
}
/*
* Clear the StringBuilder: this does not throw
* exceptions. Reuse the StringBuilder until the end of
* the current probedata then dispose of it.
*/
g_bufsetlen_jm, 0);
/* Add formatted string to PrintaRecord */
return (DTRACE_HANDLE_ABORT);
}
}
}
return (DTRACE_HANDLE_OK);
}
/*
* Return B_TRUE if the aggregation is included, B_FALSE otherwise. Only in the
* latter case might there be an exception pending.
*/
static boolean_t
{
if (jc->dtjj_aggregate_spec) {
if (!aggname) {
/* java exception pending */
return (B_FALSE);
}
aggname);
return (B_FALSE);
}
return (included);
}
return (B_TRUE);
}
/*
* Return NULL if a java exception is pending, otherwise return a new
* AggregationValue instance.
*/
static jobject
const dtrace_recdesc_t *rec)
{
if (act == DTRACEAGG_AVG) {
} else {
/* LINTED - alignment */
}
} else {
switch (act) {
case DTRACEAGG_COUNT:
break;
case DTRACEAGG_SUM:
break;
case DTRACEAGG_AVG:
break;
case DTRACEAGG_MIN:
break;
case DTRACEAGG_MAX:
break;
default:
"unexpected aggregation action: %d", act);
}
}
return (jvalue);
}
/*
* Stops the given consumer if it is running. Throws DTraceException if
* dtrace_stop() fails and no other exception is already pending. Clears and
* rethrows any pending exception in order to grab the global lock safely.
*/
void
{
int rc;
jthrowable e;
case DTJ_CONSUMER_GO:
case DTJ_CONSUMER_START:
break;
default:
return;
}
if (e) {
}
goto rethrow;
}
if (rc != DTRACE_STATUS_STOPPED) {
}
goto rethrow;
}
if (rc == -1) {
goto rethrow;
}
/* Do not wrap DTraceException */
"couldn't stop tracing: %s",
/* safe to call with pending exception */
} else {
}
if (e) {
/*
* Favor earlier pending exception over
* exception thrown in this function.
*/
}
}
}
/*
* Return Aggregate instance, or null if java exception pending.
*/
{
int rc;
/* Must not call MonitorEnter with a pending exception */
return (NULL);
}
/*
* Aggregations must be snapped, walked, and cleared atomically,
* otherwise clearing loses data accumulated since the most recent snap.
* This per-consumer lock prevents dtrace_work() from snapping or
* clearing aggregations while we're in the middle of this atomic
* operation, so we continue to hold it until done clearing.
*/
return (NULL);
}
/* release per-consumer lock */
return (NULL);
}
/*
* Snap aggregations
*
* We need to record the snaptime here for the caller. Leaving it to
* the caller to record the snaptime before calling getAggregate() may
* be inaccurate because of the indeterminate delay waiting on the
* consumer lock before calling dtrace_aggregate_snap().
*/
dtj_error_t e;
/*
* The dataDropped() ConsumerListener method can throw an
* exception in the getAggregate() thread if the drop handler is
* invoked during dtrace_aggregate_snap().
*/
/* Do not wrap exception thrown from ConsumerListener */
/* release per-consumer lock */
return (NULL);
}
/* Do not wrap DTraceException */
}
/* release per-consumer lock */
return (NULL);
}
/*
* Wrap the exception thrown from ConsumerListener in this case,
* so we can see that it unexpectedly reached this spot in
* native code (dtrace_aggregate_snap should have returned
* non-zero).
*/
/* release per-consumer lock */
return (NULL);
}
/* Create the Java representation of the aggregate snapshot. */
snaptime);
/* release per-consumer lock */
return (NULL);
}
/*
* Walk the aggregate, converting the data into Java Objects. Traverse
* in order by aggregation ID first and tuple second by using
* dtrace_aggregate_walk_keysorted (uses varkeycmp). We cannot do the
* same for aggregations generated by the printa() action, since
* dtrace_work() traverses aggregation data in the order determined by
* the various "aggsort" options. Functions used by both the consumer
* loop and the competing getAggregate() thread must not depend on the
* ordering of records by tuple key.
*
* It is impractical to hold the global lock around
* dtrace_aggregate_print(), since it may take a long time (e.g. an
* entire second) if it performs expensive conversions such as that
* needed for user stack traces. Most libdtrace functions are not
* guaranteed to be MT-safe, even when each thread has its own dtrace
* handle; or even if they are safe, there is no guarantee that future
* changes may not make them unsafe. Fortunately in this case, however,
* only a per-consumer lock is necessary to avoid conflict with
* dtrace_work() running in another thread (the consumer loop).
*/
/* release per-consumer lock */
return (NULL);
}
if (rc != 0) {
dtj_error_t e;
/* release per-consumer lock */
return (NULL);
}
if (e.dtje_number != EINTR) {
/* Do not wrap DTraceException */
/* release per-consumer lock */
return (NULL);
}
}
/* release per-consumer lock */
return (NULL);
}
/*
* dtrace_aggregate_clear() clears all aggregations, and we need to
* clear aggregations selectively. It also fails to preserve the
* lquantize() range and step size; using aggregate_walk() to clear
* aggregations does not have this problem.
*/
/* release per-consumer lock */
return (NULL);
}
if (rc != 0) {
dtj_error_t e;
/* Do not wrap DTraceException */
}
/* release per-consumer lock */
return (NULL);
}
return (NULL);
}
return (aggregate);
}
/*
* Process any requests, such as the setting of runtime options, enqueued during
* dtrace_sleep(). A Java exception is pending if this function returns
* DTJ_ERR.
*/
static dtj_status_t
{
dtj_request_t *r;
const char *opt;
const char *val;
(void) pthread_mutex_lock(list_lock);
while (!dtj_list_empty(list)) {
r = uu_list_first(list);
uu_list_remove(list, r);
switch (r->dtjr_type) {
case DTJ_REQUEST_OPTION:
val) == -1) {
/* Do not wrap DTraceException */
"failed to set %s: %s", opt,
dtj_request_destroy(r, NULL);
(void) pthread_mutex_unlock(list_lock);
return (DTJ_ERR);
}
break;
}
dtj_request_destroy(r, NULL);
}
(void) pthread_mutex_unlock(list_lock);
return (DTJ_OK);
}
/*
* Return DTJ_OK if the consumer loop is stopped normally by either the exit()
* action or the Consumer stop() method. Otherwise return DTJ_ERR if the
* consumer loop terminates abnormally with an exception pending.
*/
{
dtj_error_t e;
do {
}
/*
* Exception left pending by Consumer
* getAggregate() method.
*/
return (DTJ_ERR);
}
}
}
/*
* Functions like dtrace_setopt() are not safe to call during
* dtrace_sleep(). Check the request list every time we wake up
* from dtrace_sleep().
*/
if (!done) {
/* Do not wrap DTraceException */
return (DTJ_ERR);
}
}
/* Must not call MonitorEnter with a pending exception */
return (DTJ_ERR);
}
/*
* Use the per-consumer lock to avoid conflict with
* get_aggregate() called from another thread.
*/
return (DTJ_ERR);
}
/* Don't wrap exception thrown from ConsumerListener */
return (DTJ_ERR);
}
case DTRACE_WORKSTATUS_DONE:
break;
case DTRACE_WORKSTATUS_OKAY:
break;
default:
/*
* Check for a pending exception that got us to this
* error workstatus case.
*/
/*
* Ensure valid initial state before releasing
* the consumer lock
*/
/* Do not wrap DTraceException */
/* Release per-consumer lock */
return (DTJ_ERR);
}
/* java exception pending */
/* Release per-consumer lock */
return (DTJ_ERR);
}
if (e.dtje_number != EINTR) {
/* Do not wrap DTraceException */
/* Release per-consumer lock */
return (DTJ_ERR);
}
}
/*
* Check for ConsumerException before doing anything else with
* the JNIEnv.
*/
/*
* Do not wrap exception thrown from ConsumerListener.
*/
/* Release per-consumer lock */
return (DTJ_ERR);
}
/*
* Notify ConsumerListeners the the dtrace_work() interval ended
* before releasing the lock.
*/
/* Don't wrap exception thrown from ConsumerListener */
return (DTJ_ERR);
}
/*
* Check for a temporarily cleared exception set by a handler
* that could not safely leave the exception pending because it
* could not return an abort signal. Rethrow it now that it's
* safe to do so (when it's possible to ensure that no JNI calls
* will be made that are unsafe while an exception is pending).
*/
if (jc->dtjj_exception) {
return (DTJ_ERR);
}
} while (!done);
return (DTJ_OK);
}