/* * 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. */ package org.opensolaris.os.dtrace; import java.io.*; import java.beans.*; import java.util.*; /** * A record generated by the DTrace {@code printa()} action. Lists the * aggregations passed to {@code printa()} and records the formatted * output associated with each {@link Tuple}. If multiple aggregations * were passed to the {@code printa()} action that generated this * record, then the DTrace library tabulates the output, using a default * format if no format string was specified. By default, the output * string associated with a given {@code Tuple} includes a value from * each aggregation, or zero wherever an aggregation has no value * associated with that {@code Tuple}. For example, the D statements *

 *     @a[123] = sum(1);
 *     @b[456] = sum(2);
 *     printa(@a, @b, @c);
 * 
* produce output for the tuples "123" and "456" similar to the * following: *

 *	123	1	0	0
 *	456	0	2	0
 * 
* The first column after the tuple contains values from {@code @a}, * the next column contains values from {@code @b}, and the last * column contains zeros because {@code @c} has neither a value * associated with "123" nor a value associated with "456". *

* If a format string is passed to {@code printa()}, it may limit the * aggregation data available in this record. For example, if the * format string specifies a value placeholder for only one of two * aggregations passed to {@code printa()}, then the resulting {@code * PrintaRecord} will contain only one {@code Aggregation}. If no value * placeholder is specified, or if the aggregation tuple is not * completely specified, the resulting {@code PrintaRecord} will contain * no aggregation data. However, the formatted output generated by the * DTrace library is available in all cases. For details about * {@code printa()} format strings, see the * {@code printa()} section of the Output * Formatting chapter of the Dynamic Tracing Guide. *

* Immutable. Supports persistence using {@link java.beans.XMLEncoder}. * * @author Tom Erickson */ public final class PrintaRecord implements Record, Serializable, Comparable { static final long serialVersionUID = -4174277639915895694L; static { try { BeanInfo info = Introspector.getBeanInfo(PrintaRecord.class); PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate( new String[] {"snaptime", "aggregations", "formattedStrings", "tuples", "output"}) { /* * Need to prevent DefaultPersistenceDelegate from using * overridden equals() method, resulting in a * StackOverFlowError. Revert to PersistenceDelegate * implementation. See * http://forum.java.sun.com/thread.jspa?threadID= * 477019&tstart=135 */ protected boolean mutatesTo(Object oldInstance, Object newInstance) { return (newInstance != null && oldInstance != null && oldInstance.getClass() == newInstance.getClass()); } }; BeanDescriptor d = info.getBeanDescriptor(); d.setValue("persistenceDelegate", persistenceDelegate); } catch (IntrospectionException e) { System.out.println(e); } } /** @serial */ private final long snaptime; /** @serial */ private List aggregations; /** @serial */ private Map formattedStrings; /** @serial */ private List tuples; private transient StringBuilder outputBuffer; private transient String output; private transient boolean formatted; /** * Package level access, called by ProbeData. */ PrintaRecord(long snaptimeNanos, boolean isFormatString) { snaptime = snaptimeNanos; aggregations = new ArrayList (); formattedStrings = new HashMap (); tuples = new ArrayList (); outputBuffer = new StringBuilder(); formatted = isFormatString; validate(); } /** * Creates a record with the given snaptime, aggregations, and * formatted output. * * @param snaptimeNanos nanosecond timestamp of the snapshot used * to create this {@code printa()} record * @param aggs aggregations passed to the {@code printa()} action * that generated this record * @param formattedOutput the formatted output, if any, associated * with each {@code Tuple} occurring in the aggregations belonging * to this record, one formatted string per {@code Tuple}, or an * empty or {@code null} map if an incomplete {@code printa()} * format string caused aggregation tuples to be omitted from this * record * @param orderedTuples list of aggregation tuples in the same order * generated by the native DTrace library (determined by the various * "aggsort" options such as {@link Option#aggsortkey}) * @param formattedOutputString {@code printa()} formatted string * output in the same order generated by the native DTrace library * (determined by the various "aggsort" options such as * {@link Option#aggsortkey}) * @throws NullPointerException if the given collection of * aggregations is {@code null}, or if the given ordered lists of * tuples or formatted strings are {@code null} * @throws IllegalArgumentException if the given snaptime is * negative */ public PrintaRecord(long snaptimeNanos, Collection aggs, Map formattedOutput, List orderedTuples, String formattedOutputString) { snaptime = snaptimeNanos; if (aggs != null) { aggregations = new ArrayList (aggs.size()); aggregations.addAll(aggs); } if (formattedOutput != null) { formattedStrings = new HashMap (formattedOutput); } if (orderedTuples != null) { tuples = new ArrayList (orderedTuples.size()); tuples.addAll(orderedTuples); } output = formattedOutputString; validate(); } private final void validate() { if (snaptime < 0) { throw new IllegalArgumentException("snaptime is negative"); } if (aggregations == null) { throw new NullPointerException("aggregations list is null"); } Aggregation a; for (int i = 0, len = aggregations.size(); i < len; ++i) { a = aggregations.get(i); if (a == null) { throw new NullPointerException( "null aggregation at index " + i); } } if (tuples == null) { throw new NullPointerException("ordered tuple list is null"); } if (output == null && outputBuffer == null) { throw new NullPointerException("formatted output is null"); } } /** * Gets the nanosecond timestamp of the aggregate snapshot used to * create this {@code printa()} record. * * @return nanosecond timestamp */ public long getSnaptime() { return snaptime; } private Aggregation getAggregationImpl(String name) { if (name == null) { return null; } for (Aggregation a : aggregations) { if (name.equals(a.getName())) { return a; } } return null; } /** * Gets the named aggregation. * * @return the named aggregation passed to {@code printa()}, or * {@code null} if the named aggregation is not passed to {@code * printa()}, or if it is omitted due to an incomplete {@code * printa()} format string, or if it is empty (a future release of * this API may represent an empty DTrace aggregation as a non-null * {@code Aggregation} with no records; users of this API should not * rely on a non-null return value to indicate a non-zero record * count) */ public Aggregation getAggregation(String name) { name = Aggregate.filterUnnamedAggregationName(name); return getAggregationImpl(name); } /** * Gets a list of the aggregations passed to the {@code printa()} * action that generated this record. The returned list is a copy, * and modifying it has no effect on this record. Supports XML * persistence. * * @return non-null, possibly empty list of aggregations belonging * to this record (empty aggregations are excluded) */ public List getAggregations() { return new ArrayList (aggregations); } /** * Gets the formatted string, if any, associated with the given * aggregation tuple. * * @param key aggregation tuple * @return the formatted string associated with the given * aggregation tuple, or {@code null} if the given tuple does not * exist in the aggregations belonging to this record or if it * is omitted from this record due to an incomplete {@code printa()} * format string * @see #getFormattedStrings() * @see #getOutput() */ public String getFormattedString(Tuple key) { if (formattedStrings == null) { return null; } return formattedStrings.get(key); } /** * Gets the formatted output, if any, associated with each {@code * Tuple} occurring in the aggregations belonging to this record, * one formatted string per {@code Tuple}. Gets an empty map if * aggregation tuples are omitted from this record due to an * incomplete {@code printa()} format string. The returned map is a * copy and modifying it has no effect on this record. Supports XML * persistence. * * @return a map of aggregation tuples and their associated * formatted output strings, empty if aggregation tuples are omitted * from this record due to an incomplete {@code printa(}) format * string * @see #getFormattedString(Tuple key) * @see #getOutput() */ public Map getFormattedStrings() { if (formattedStrings == null) { return new HashMap (); } return new HashMap (formattedStrings); } /** * Gets an ordered list of this record's aggregation tuples. The * returned list is a copy, and modifying it has no effect on this * record. Supports XML persistence. * * @return a non-null list of this record's aggregation tuples in * the order they were generated by the native DTrace library, as * determined by the {@link Option#aggsortkey}, {@link * Option#aggsortrev}, {@link Option#aggsortpos}, and {@link * Option#aggsortkeypos} options */ public List getTuples() { return new ArrayList (tuples); } /** * Gets this record's formatted output. Supports XML persistence. * * @return non-null formatted output in the order generated by the * native DTrace library, as determined by the {@link * Option#aggsortkey}, {@link Option#aggsortrev}, {@link * Option#aggsortpos}, and {@link Option#aggsortkeypos} options */ public String getOutput() { if (output == null) { output = outputBuffer.toString(); outputBuffer = null; if ((output.length() == 0) && !formatted) { output = "\n"; } } return output; } /** * Package level access, called by ProbeData. * * @throws NullPointerException if aggregationName is null * @throws IllegalStateException if this PrintaRecord has an * aggregation matching the given name and it already has an * AggregationRecord with the same tuple key as the given record. */ void addRecord(String aggregationName, long aggid, AggregationRecord record) { if (formattedStrings == null) { // printa() format string does not completely specify tuple return; } aggregationName = Aggregate.filterUnnamedAggregationName( aggregationName); Aggregation aggregation = getAggregationImpl(aggregationName); if (aggregation == null) { aggregation = new Aggregation(aggregationName, aggid); aggregations.add(aggregation); } try { aggregation.addRecord(record); } catch (IllegalArgumentException e) { Map map = aggregation.asMap(); AggregationRecord r = map.get(record.getTuple()); // // The printa() format string may specify the value of the // aggregating action multiple times. While that changes // the resulting formatted string associated with the tuple, // we ignore the attempt to add the redundant record to the // aggregation. // if (!r.equals(record)) { throw e; } } } // // Called from native code when the tuple is not completely // specified in the printa() format string. // void invalidate() { formattedStrings = null; aggregations.clear(); tuples.clear(); } void addFormattedString(Tuple tuple, String formattedString) { if (tuple != null && formattedStrings != null) { if (formattedStrings.containsKey(tuple)) { throw new IllegalArgumentException("A formatted string " + "for tuple " + tuple + " already exists."); } else { formattedStrings.put(tuple, formattedString); tuples.add(tuple); } } outputBuffer.append(formattedString); } /** * Compares the specified object with this {@code PrintaRecord} for * equality. Returns {@code true} if and only if the specified * object is also a {@code PrintaRecord} and both records have the * same aggregations and the same formatted strings in the same * order (by aggregation tuple). * * @return {@code true} if and only if the specified object is also * a {@code PrintaRecord} and both records have the same * aggregations and the same formatted strings in the same order (by * aggregation tuple) */ @Override public boolean equals(Object o) { if (o instanceof PrintaRecord) { PrintaRecord r = (PrintaRecord)o; return (aggregations.equals(r.aggregations) && ((formattedStrings == null || formattedStrings.isEmpty()) ? (r.formattedStrings == null || r.formattedStrings.isEmpty()) : formattedStrings.equals(r.formattedStrings)) && tuples.equals(r.tuples)); } return false; } /** * Overridden to ensure that equal instances have equal hash codes. */ @Override public int hashCode() { int hash = 17; hash = (hash * 37) + aggregations.hashCode(); hash = (hash * 37) + ((formattedStrings == null || formattedStrings.isEmpty()) ? 0 : formattedStrings.hashCode()); hash = (hash * 37) + tuples.hashCode(); return hash; } /** * Compares the formatted {@link #getOutput() output} of this record * with that of the given record. Note that ordering {@code printa} * records by their output string values is incompatible with {@link * #equals(Object o) equals()}, which also checks the underlying * aggregation data for equality. * * @return a negative number, 0, or a positive number as this * record's formatted output is lexicographically less than, equal * to, or greater than the given record's formatted output */ public int compareTo(PrintaRecord r) { return getOutput().compareTo(r.getOutput()); } /** * Serialize this {@code PrintaRecord} instance. * * @serialData Serialized fields are emitted, followed by the * formatted output string. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); if (output == null) { s.writeObject(outputBuffer.toString()); } else { s.writeObject(output); } } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); output = (String)s.readObject(); // make defensive copy if (aggregations != null) { List copy = new ArrayList (aggregations.size()); copy.addAll(aggregations); aggregations = copy; } if (formattedStrings != null) { formattedStrings = new HashMap (formattedStrings); } if (tuples != null) { List copy = new ArrayList (tuples.size()); copy.addAll(tuples); tuples = copy; } // check constructor invariants only after defensive copy try { validate(); } catch (Exception e) { InvalidObjectException x = new InvalidObjectException( e.getMessage()); x.initCause(e); throw x; } } /** * Gets a string representation of this 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(PrintaRecord.class.getName()); buf.append("[snaptime = "); buf.append(snaptime); buf.append(", aggregations = "); buf.append(aggregations); buf.append(", formattedStrings = "); buf.append(formattedStrings); buf.append(", tuples = "); buf.append(tuples); buf.append(", output = "); buf.append(getOutput()); buf.append(']'); return buf.toString(); } }