/* * 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. * * ident "%Z%%M% %I% %E% SMI" */ package org.opensolaris.os.dtrace; import java.util.*; import java.io.*; import java.beans.*; /** * Data generated when a DTrace probe fires, contains one record for * every record-generating action in the probe. (Some D actions, such * as {@code clear()}, do not generate a {@code ProbeData} record.) A * {@link Consumer} gets data from DTrace by registering a {@link * ConsumerListener listener} to get probe data whenever a probe fires: *

 *     Consumer consumer = new LocalConsumer();
 *     consumer.addConsumerListener(new ConsumerAdapter() {
 *         public void dataReceived(DataEvent e) {
 *             ProbeData probeData = e.getProbeData();
 *             System.out.println(probeData);
 *         }
 *     });
 * 
* Getting DTrace to generate that probe data involves compiling, * enabling, and running a D program: *

 *     try {
 *         consumer.open();
 *         consumer.compile(program);
 *         consumer.enable(); // instruments code at matching probe points
 *         consumer.go(); // non-blocking; generates probe data in background
 *     } catch (DTraceException e) {
 *         e.printStackTrace();
 *     }
 * 
* Currently the {@code ProbeData} instance does not record a timestamp. * If you need a timestamp, trace the built-in {@code timestamp} * variable in your D program. (See the * * Built-in Variables section of the Variables chapter of * the Solaris Dynamic Tracing Guide). *

* Immutable. Supports persistence using {@link java.beans.XMLEncoder}. * * @see Consumer#addConsumerListener(ConsumerListener l) * @see ConsumerListener#dataReceived(DataEvent e) * * @author Tom Erickson */ public final class ProbeData implements Serializable, Comparable { static final long serialVersionUID = -7021504416192099215L; static { try { BeanInfo info = Introspector.getBeanInfo(ProbeData.class); PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate( new String[] {"enabledProbeID", "CPU", "enabledProbeDescription", "flow", "records"}); BeanDescriptor d = info.getBeanDescriptor(); d.setValue("persistenceDelegate", persistenceDelegate); } catch (IntrospectionException e) { System.out.println(e); } } private static Comparator DEFAULT_CMP; static { try { DEFAULT_CMP = ProbeData.getComparator(KeyField.RECORDS, KeyField.EPID); } catch (Throwable e) { e.printStackTrace(); System.exit(1); } } /** @serial */ private int epid; /** @serial */ private int cpu; /** @serial */ private ProbeDescription enabledProbeDescription; /** @serial */ private Flow flow; // Scratch data, one element per native probedata->dtpda_edesc->dtepd_nrecs // element, cleared after records list is fully populated. private transient List nativeElements; /** @serial */ private List records; /** * Enumerates the fields by which {@link ProbeData} may be sorted * using the {@link #getComparator(KeyField[] f) getComparator()} * convenience method. */ public enum KeyField { /** Specifies {@link ProbeData#getCPU()} */ CPU, /** Specifies {@link ProbeData#getEnabledProbeDescription()} */ PROBE, /** Specifies {@link ProbeData#getEnabledProbeID()} */ EPID, /** Specifies {@link ProbeData#getRecords()} */ RECORDS } /** * Called by native code. */ private ProbeData(int enabledProbeID, int cpuID, ProbeDescription p, Flow f, int nativeElementCount) { epid = enabledProbeID; cpu = cpuID; enabledProbeDescription = p; flow = f; nativeElements = new ArrayList (nativeElementCount); records = new ArrayList (); validate(); } /** * Creates a probe data instance with the given properties and list * of records. Supports XML persistence. * * @param enabledProbeID identifies the enabled probe that fired; * the ID is generated by the native DTrace library to distinguish * all probes enabled by the source consumer (as opposed to * all probes on the system) * @param cpuID non-negative ID, identifies the CPU on which the * probe fired * @param p identifies the enabled probe that fired * @param f current state of control flow (entry or return and depth * in call stack) at time of probe firing, included if {@link * Option#flowindent flowindent} option used, {@code null} otherwise * @param recordList list of records generated by D actions in the * probe that fired, one record per action, may be empty * @throws NullPointerException if the given probe description or * list of records is {@code null} */ public ProbeData(int enabledProbeID, int cpuID, ProbeDescription p, Flow f, List recordList) { epid = enabledProbeID; cpu = cpuID; enabledProbeDescription = p; flow = f; records = new ArrayList (recordList.size()); records.addAll(recordList); validate(); } private final void validate() { if (enabledProbeDescription == null) { throw new NullPointerException( "enabled probe description is null"); } if (records == null) { throw new NullPointerException("record list is null"); } } private void addDataElement(Record o) { // Early error detection if native code adds the wrong type Record r = Record.class.cast(o); nativeElements.add(o); } /** * Called by native code. */ private void addRecord(Record record) { records.add(record); } /** * Called by native code. */ private void addTraceRecord(int i) { // trace() value is preceded by one null for every D program // statement preceding trace() that is not a D action, such as // assignment to a variable (results in a native probedata // record with no data). int len = nativeElements.size(); Record rec = null; for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i); records.add(rec); } /** * Called by native code. */ private void addSymbolRecord(int i, String lookupString) { int len = nativeElements.size(); Record rec = null; for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i); SymbolValueRecord symbol = SymbolValueRecord.class.cast(rec); if (symbol instanceof KernelSymbolRecord) { KernelSymbolRecord.class.cast(symbol).setSymbol(lookupString); } else if (symbol instanceof UserSymbolRecord) { UserSymbolRecord.class.cast(symbol).setSymbol(lookupString); } else { throw new IllegalStateException("no symbol record at index " + i); } records.add(symbol); } /** * Called by native code. */ private void addStackRecord(int i, String framesString) { int len = nativeElements.size(); Record rec = null; for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i); StackValueRecord stack = StackValueRecord.class.cast(rec); StackFrame[] frames = KernelStackRecord.parse(framesString); if (stack instanceof KernelStackRecord) { KernelStackRecord.class.cast(stack).setStackFrames(frames); } else if (stack instanceof UserStackRecord) { UserStackRecord.class.cast(stack).setStackFrames(frames); } else { throw new IllegalStateException("no stack record at index " + i); } records.add(stack); } /** * Called by native code. */ private void addPrintfRecord() { records.add(new PrintfRecord()); } /** * Called by native code. */ private void addPrintaRecord(long snaptimeNanos, boolean isFormatString) { records.add(new PrintaRecord(snaptimeNanos, isFormatString)); } private PrintaRecord getLastPrinta() { ListIterator itr = records.listIterator(records.size()); PrintaRecord printa = null; Record record; while (itr.hasPrevious() && (printa == null)) { record = itr.previous(); if (record instanceof PrintaRecord) { printa = PrintaRecord.class.cast(record); } } return printa; } /** * Called by native code. */ private void addAggregationRecord(String aggregationName, long aggid, AggregationRecord rec) { PrintaRecord printa = getLastPrinta(); if (printa == null) { throw new IllegalStateException( "No PrintaRecord in this ProbeData"); } printa.addRecord(aggregationName, aggid, rec); } /** * Called by native code. */ private void invalidatePrintaRecord() { PrintaRecord printa = getLastPrinta(); if (printa == null) { throw new IllegalStateException( "No PrintaRecord in this ProbeData"); } printa.invalidate(); } /** * Called by native code. */ private void addPrintaFormattedString(Tuple tuple, String s) { PrintaRecord printa = getLastPrinta(); if (printa == null) { throw new IllegalStateException( "No PrintaRecord in this ProbeData"); } printa.addFormattedString(tuple, s); } /** * Called by native code. */ private void addExitRecord(int i) { int len = nativeElements.size(); Record rec = null; for (; ((rec = nativeElements.get(i)) == null) && (i < len); ++i); ScalarRecord scalar = ScalarRecord.class.cast(rec); Integer exitStatus = Integer.class.cast(scalar.getValue()); records.add(new ExitRecord(exitStatus)); } /** * Called by native code. Attaches native probedata elements cached * between the given first index and last index inclusive to the most * recently added record if applicable. */ private void attachRecordElements(int first, int last) { Record record = records.get(records.size() - 1); if (record instanceof PrintfRecord) { PrintfRecord printf = PrintfRecord.class.cast(record); Record e; for (int i = first; i <= last; ++i) { e = nativeElements.get(i); if (e == null) { // printf() unformatted elements are preceded by one // null for every D program statement preceding the // printf() that is not a D action, such as // assignment to a variable (generates a probedata // record with no data). continue; } printf.addUnformattedElement(ScalarRecord.class.cast(e)); } } } /** * Called by native code. */ void clearNativeElements() { nativeElements = null; } /** * Called by native code. */ private void setFormattedString(String s) { Record record = records.get(records.size() - 1); if (record instanceof PrintfRecord) { PrintfRecord printf = PrintfRecord.class.cast(record); printf.setFormattedString(s); } } /** * Convenience method, gets a comparator that sorts multiple {@link * ProbeDescription} instances by the specified field or fields. If * more than one sort field is specified, the probe data are sorted * by the first field, and in case of a tie, by the second field, * and so on, in the order that the fields are specified. * * @param f field specifiers given in descending order of sort * priority; lower priority fields are only compared (as a tie * breaker) when all higher priority fields are equal * @return non-null probe data comparator that sorts by the * specified sort fields in the given order */ public static Comparator getComparator(KeyField ... f) { return new Cmp(f); } private static class Cmp implements Comparator { private KeyField[] sortFields; private Cmp(KeyField ... f) { sortFields = f; } public int compare(ProbeData d1, ProbeData d2) { return ProbeData.compare(d1, d2, sortFields); } } static int compareUnsigned(int i1, int i2) { int cmp; if (i1 < 0) { if (i2 < 0) { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } else { cmp = 1; // negative > positive } } else if (i2 < 0) { cmp = -1; // positive < negative } else { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } return cmp; } static int compareUnsigned(long i1, long i2) { int cmp; if (i1 < 0) { if (i2 < 0) { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } else { cmp = 1; // negative > positive } } else if (i2 < 0) { cmp = -1; // positive < negative } else { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } return cmp; } static int compareUnsigned(byte i1, byte i2) { int cmp; if (i1 < 0) { if (i2 < 0) { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } else { cmp = 1; // negative > positive } } else if (i2 < 0) { cmp = -1; // positive < negative } else { cmp = (i1 < i2 ? -1 : (i1 > i2 ? 1 : 0)); } return cmp; } static int compareByteArrays(byte[] a1, byte[] a2) { int cmp = 0; int len1 = a1.length; int len2 = a2.length; for (int i = 0; (cmp == 0) && (i < len1) && (i < len2); ++i) { cmp = compareUnsigned(a1[i], a2[i]); } if (cmp == 0) { cmp = (len1 < len2 ? -1 : (len1 > len2 ? 1 : 0)); } return cmp; } @SuppressWarnings("unchecked") static int compareUnsigned(Comparable v1, Comparable v2) { int cmp; if (v1 instanceof Integer) { int i1 = Integer.class.cast(v1); int i2 = Integer.class.cast(v2); cmp = compareUnsigned(i1, i2); } else if (v1 instanceof Long) { long i1 = Long.class.cast(v1); long i2 = Long.class.cast(v2); cmp = compareUnsigned(i1, i2); } else { cmp = v1.compareTo(v2); } return cmp; } /** * @throws ClassCastException if records or their data are not * mutually comparable */ @SuppressWarnings("unchecked") private static int compareRecords(Record r1, Record r2) { int cmp; if (r1 instanceof ScalarRecord) { ScalarRecord t1 = ScalarRecord.class.cast(r1); ScalarRecord t2 = ScalarRecord.class.cast(r2); Object o1 = t1.getValue(); Object o2 = t2.getValue(); if (o1 instanceof byte[]) { byte[] a1 = byte[].class.cast(o1); byte[] a2 = byte[].class.cast(o2); cmp = compareByteArrays(a1, a2); } else { Comparable v1 = Comparable.class.cast(o1); Comparable v2 = Comparable.class.cast(o2); cmp = v1.compareTo(v2); // compare signed values } } else if (r1 instanceof Comparable) { // StackValueRecord, SymbolValueRecord Comparable v1 = Comparable.class.cast(r1); Comparable v2 = Comparable.class.cast(r2); cmp = v1.compareTo(v2); } else if (r1 instanceof ExitRecord) { ExitRecord e1 = ExitRecord.class.cast(r1); ExitRecord e2 = ExitRecord.class.cast(r2); int status1 = e1.getStatus(); int status2 = e2.getStatus(); cmp = (status1 < status2 ? -1 : (status1 > status2 ? 1 : 0)); } else { // PrintfRecord, PrintaRecord r1.getClass().cast(r2); String s1 = r1.toString(); String s2 = r2.toString(); cmp = s1.compareTo(s2); } return cmp; } /** * @throws ClassCastException if lists are not mutually comparable * because corresponding list elements are not comparable or the * list themselves are different lengths */ private static int compareRecordLists(ProbeData d1, ProbeData d2) { List list1 = d1.getRecords(); List list2 = d2.getRecords(); int len1 = list1.size(); int len2 = list2.size(); if (len1 != len2) { throw new ClassCastException("Record lists of different " + "length are not comparable (lengths are " + len1 + " and " + len2 + ")."); } int cmp; Record r1; Record r2; for (int i = 0; (i < len1) && (i < len2); ++i) { r1 = list1.get(i); r2 = list2.get(i); cmp = compareRecords(r1, r2); if (cmp != 0) { return cmp; } } return 0; } private static int compare(ProbeData d1, ProbeData d2, KeyField[] comparedFields) { int cmp; for (KeyField f : comparedFields) { switch (f) { case CPU: int cpu1 = d1.getCPU(); int cpu2 = d2.getCPU(); cmp = (cpu1 < cpu2 ? -1 : (cpu1 > cpu2 ? 1 : 0)); break; case PROBE: ProbeDescription p1 = d1.getEnabledProbeDescription(); ProbeDescription p2 = d2.getEnabledProbeDescription(); cmp = p1.compareTo(p2); break; case EPID: int epid1 = d1.getEnabledProbeID(); int epid2 = d2.getEnabledProbeID(); cmp = (epid1 < epid2 ? -1 : (epid1 > epid2 ? 1 : 0)); break; case RECORDS: cmp = compareRecordLists(d1, d2); break; default: throw new IllegalArgumentException( "Unexpected sort field " + f); } if (cmp != 0) { return cmp; } } return 0; } /** * Gets the enabled probe ID. Identifies the enabled probe that * fired and generated this {@code ProbeData}. The "epid" is * different from {@link ProbeDescription#getID()} in that it * identifies a probe among all probes enabled by the source {@link * Consumer}, rather than among all the probes on the system. * * @return the enabled probe ID generated by the native DTrace * library */ public int getEnabledProbeID() { return epid; } /** * Gets the ID of the CPU on which the probe fired. * * @return ID of the CPU on which the probe fired */ public int getCPU() { return cpu; } /** * Gets the enabled probe description. Identifies the enabled probe * that fired and generated this {@code ProbeData}. * * @return non-null probe description */ public ProbeDescription getEnabledProbeDescription() { return enabledProbeDescription; } /** * Gets the current state of control flow (function entry or return, * and depth in call stack) at the time of the probe firing that * generated this {@code ProbeData} instance, or {@code null} if * such information was not requested with the {@code flowindent} * option. * * @return a description of control flow across function boundaries, * or {@code null} if {@code Consumer.getOption(Option.flowindent)} * returns {@link Option#UNSET} * @see Consumer#setOption(String option) * @see Option#flowindent */ public Flow getFlow() { return flow; } /** * Gets the records generated by the actions of the probe that * fired, in the same order as the actions that generated the * records. The returned list includes one record for every * record-generating D action (some D actions, such as {@code * clear()}, do not generate records). * * @return non-null, unmodifiable list view of the records belonging * to this {@code ProbeData} in the order of the actions in the * DTrace probe that generated them (record-producing actions are * generally those that produce output, such as {@code printf()}, * but also the {@code exit()} action) */ public List getRecords() { return Collections. unmodifiableList(records); } /** * Natural ordering of probe data. Sorts probe data by records * first, then if record data is equal, by enabled probe ID. * * @param d probe data to be compared with this probe data * @return a negative number, zero, or a positive number as this * probe data is less than, equal to, or greater than the given * probe data * @see ProbeData#getComparator(KeyField[] f) * @throws NullPointerException if the given probe data is * {@code null} * @throws ClassCastException if record lists of both {@code * ProbeData} instances are not mutually comparable because * corresponding list elements are not comparable or the lists * themselves are different lengths */ public int compareTo(ProbeData d) { return DEFAULT_CMP.compare(this, d); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // Defensively copy record list _before_ validating. int len = records.size(); ArrayList copy = new ArrayList (len); copy.addAll(records); records = copy; // Check class invariants try { validate(); } catch (Exception e) { InvalidObjectException x = new InvalidObjectException( e.getMessage()); x.initCause(e); throw x; } } /** * Gets a string representation of this {@code ProbeData} instance * useful for logging and not intended for display. The exact * details of the representation are unspecified and subject to * change, but the following format may be regarded as typical: *


     * class-name[property1 = value1, property2 = value2]
     * 
*/ public String toString() { StringBuilder buf = new StringBuilder(); buf.append(ProbeData.class.getName()); buf.append("[epid = "); buf.append(epid); buf.append(", cpu = "); buf.append(cpu); buf.append(", enabledProbeDescription = "); buf.append(enabledProbeDescription); buf.append(", flow = "); buf.append(flow); buf.append(", records = "); Record record; Object value; buf.append('['); for (int i = 0; i < records.size(); ++i) { if (i > 0) { buf.append(", "); } record = records.get(i); if (record instanceof ValueRecord) { value = ValueRecord.class.cast(record).getValue(); if (value instanceof String) { buf.append("\""); buf.append(String.class.cast(value)); buf.append("\""); } else { buf.append(record); } } else { buf.append(record); } } buf.append(']'); buf.append(']'); return buf.toString(); } }