PrintaRecord.java revision fb3fb4f3d76d55b64440afd0af72775dfad3bd1d
/*
* 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 2006 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.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
* <pre><code>
* &#64;a[123] = sum(1);
* &#64;b[456] = sum(2);
* printa(&#64;a, &#64;b, &#64;c);
* </code></pre>
* produce output for the tuples "123" and "456" similar to the
* following:
* <pre><code>
* 123 1 0 0
* 456 0 2 0
* </code></pre>
* 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".
* <p>
* 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 <a
* href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidli3?a=view>
* <b>{@code printa()}</b></a> section of the <b>Output
* Formatting</b> chapter of the <i>Solaris Dynamic Tracing Guide</i>.
* <p>
* Immutable. Supports persistence using {@link java.beans.XMLEncoder}.
*
* @author Tom Erickson
*/
public final class PrintaRecord implements Record, Serializable {
static final long serialVersionUID = -4174277639915895694L;
static {
try {
BeanInfo info = Introspector.getBeanInfo(PrintaRecord.class);
PersistenceDelegate persistenceDelegate =
new DefaultPersistenceDelegate(
new String[] {"snaptime", "aggregations",
"formattedStrings", "tuples", "output"});
BeanDescriptor d = info.getBeanDescriptor();
d.setValue("persistenceDelegate", persistenceDelegate);
} catch (IntrospectionException e) {
System.out.println(e);
}
}
/** @serial */
private final long snaptime;
/** @serial */
private List <Aggregation> aggregations;
/** @serial */
private Map <Tuple, String> formattedStrings;
/** @serial */
private List <Tuple> tuples;
private transient StringBuffer 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 <Aggregation> ();
formattedStrings = new HashMap <Tuple, String> ();
tuples = new ArrayList <Tuple> ();
outputBuffer = new StringBuffer();
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 <Aggregation> aggs,
Map <Tuple, String> formattedOutput,
List <Tuple> orderedTuples,
String formattedOutputString)
{
snaptime = snaptimeNanos;
if (aggs != null) {
aggregations = new ArrayList <Aggregation> (aggs.size());
aggregations.addAll(aggs);
}
if (formattedOutput != null) {
formattedStrings = new HashMap <Tuple, String>
(formattedOutput);
}
if (orderedTuples != null) {
tuples = new ArrayList <Tuple> (orderedTuples.size());
tuples.addAll(orderedTuples);
}
output = formattedOutputString;
validate();
}
private 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 <Aggregation>
getAggregations()
{
return new ArrayList <Aggregation> (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 <Tuple, String>
getFormattedStrings()
{
if (formattedStrings == null) {
return new HashMap <Tuple, String> ();
}
return new HashMap <Tuple, String> (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 <Tuple>
getTuples()
{
return new ArrayList <Tuple> (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 <Tuple, AggregationRecord> 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);
}
/**
* 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 <Aggregation> copy = new ArrayList <Aggregation>
(aggregations.size());
copy.addAll(aggregations);
aggregations = copy;
}
if (formattedStrings != null) {
formattedStrings = new HashMap <Tuple, String> (formattedStrings);
}
if (tuples != null) {
List <Tuple> copy = new ArrayList <Tuple> (tuples.size());
copy.addAll(tuples);
tuples = copy;
}
// check constructor invariants only after defensize copy
try {
validate();
} catch (Exception e) {
throw new InvalidObjectException(e.getMessage());
}
}
/**
* 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:
* <pre><code>
* class-name[property1 = value1, property2 = value2]
* </code></pre>
*/
public String
toString()
{
StringBuffer buf = new StringBuffer();
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();
}
}