Home | History | Annotate | Download | only in dtrace
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  *
     26  * ident	"%Z%%M%	%I%	%E% SMI"
     27  */
     28 package org.opensolaris.os.dtrace;
     29 
     30 import java.util.*;
     31 import java.beans.*;
     32 import java.io.*;
     33 
     34 /**
     35  * A snapshot of a DTrace aggregation.  The name of an {@code
     36  * Aggregation} instance matches the source declaration, for example
     37  * <pre>        {@code @a[execname] = count();}</pre>
     38  * results in an {@code Aggregation} named "a" (the name does not
     39  * include the preceding {@code @}).  For convenience, a single
     40  * aggregation can remain unnamed (multiple aggregations in the same D
     41  * program need distinct names).  The unnamed aggregation results in an
     42  * {@code Aggregation} instance whose name is the empty string, for
     43  * example
     44  * <pre>        {@code @[execname] = count();}</pre>
     45  * An aggregation can list more than one variable in square brackets in
     46  * order to accumulate a value for each unique combination, or {@link
     47  * Tuple}.  Each tuple instance is associated with its accumulated
     48  * {@link AggregationValue} in an {@link AggregationRecord}.  For
     49  * example
     50  * <pre>        {@code @counts[execname, probefunc, cpu] = count();}</pre>
     51  * results in an {@code Aggregation} named "counts" containing records
     52  * each pairing a {@link CountValue} to a three-element {@code Tuple}.
     53  * It is also possible to omit the square brackets, for example
     54  * <pre>        {@code @a = count();}</pre>
     55  * results in an {@code Aggregation} named "a" with only a single record
     56  * keyed to the empty tuple ({@link Tuple#EMPTY}).
     57  * <p>
     58  * For more information, see the <a
     59  * href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidlh7?a=view>
     60  * <b>Aggregations</b></a> chapter of the <i>Solaris Dynamic Tracing
     61  * Guide</i>.  Also, the <a
     62  * href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidlfv?a=view>
     63  * <b>Built-in Variables</b></a> section of the <b>Variables</b> chapter
     64  * describes variables like {@code execname}, {@code probefunc}, and
     65  * {@code cpu} useful for aggregating.
     66  * <p>
     67  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
     68  *
     69  * @see Aggregate
     70  * @see PrintaRecord
     71  *
     72  * @author Tom Erickson
     73  */
     74 public final class Aggregation implements Serializable {
     75     static final long serialVersionUID = 2340811719178724026L;
     76 
     77     static {
     78 	try {
     79 	    BeanInfo info = Introspector.getBeanInfo(Aggregation.class);
     80 	    PersistenceDelegate persistenceDelegate =
     81 		    new DefaultPersistenceDelegate(
     82 		    new String[] {"name", "ID", "records"})
     83 	    {
     84 		@Override
     85 		protected boolean
     86 		mutatesTo(Object oldInstance, Object newInstance)
     87 		{
     88 		    return ((newInstance != null) && (oldInstance != null) &&
     89 			    (oldInstance.getClass() == newInstance.getClass()));
     90 		}
     91 	    };
     92 	    BeanDescriptor d = info.getBeanDescriptor();
     93 	    d.setValue("persistenceDelegate", persistenceDelegate);
     94 	} catch (IntrospectionException e) {
     95 	    e.printStackTrace();
     96 	}
     97     }
     98 
     99     /** @serial */
    100     private String name;
    101     /** @serial */
    102     private final long id;
    103     private transient Map <Tuple, AggregationRecord> map;
    104 
    105     /**
    106      * Package-level access, called by Aggregate
    107      */
    108     Aggregation(String aggregationName, long aggregationID)
    109     {
    110 	name = Aggregate.filterUnnamedAggregationName(aggregationName);
    111 	id = aggregationID;
    112 	map = new HashMap <Tuple, AggregationRecord> ();
    113     }
    114 
    115     /**
    116      * Creates an aggregation with the given name, ID, and records.
    117      * Supports XML persistence.
    118      *
    119      * @param aggregationName the name of this aggregation, empty string
    120      * if this aggregation is unnamed
    121      * @param aggregationID ID generated from a sequence by the native
    122      * DTrace library
    123      * @param aggregationRecords unordered collection of records
    124      * belonging to this aggregation
    125      * @throws NullPointerException if the specified name or list of
    126      * records is {@code null}
    127      * @throws IllegalArgumentException if any record has an empty
    128      * tuple, unless it is the only record in the given collection (only
    129      * a singleton generated by an aggregation without square brackets
    130      * uses {@link Tuple#EMPTY} as a key)
    131      * @see #getRecord(Tuple key)
    132      */
    133     public
    134     Aggregation(String aggregationName, long aggregationID,
    135 	    Collection <AggregationRecord> aggregationRecords)
    136     {
    137 	name = Aggregate.filterUnnamedAggregationName(aggregationName);
    138 	id = aggregationID;
    139 	mapRecords(aggregationRecords);
    140 	validate();
    141     }
    142 
    143     // assumes map is not yet created
    144     private void
    145     mapRecords(Collection <AggregationRecord> records)
    146     {
    147 	int capacity = (int)(((float)records.size() * 3.0f) / 2.0f);
    148 	// avoid rehashing and optimize lookup; will never be modified
    149 	map = new HashMap <Tuple, AggregationRecord> (capacity, 1.0f);
    150 	for (AggregationRecord record : records) {
    151 	    map.put(record.getTuple(), record);
    152 	}
    153     }
    154 
    155     private final void
    156     validate()
    157     {
    158 	if (name == null) {
    159 	    throw new NullPointerException("name is null");
    160 	}
    161 	for (AggregationRecord r : map.values()) {
    162 	    if ((r.getTuple().size() == 0) && (map.size() > 1)) {
    163 		throw new IllegalArgumentException("empty tuple " +
    164 			"allowed only in singleton aggregation");
    165 	    }
    166 	}
    167     }
    168 
    169     /**
    170      * Gets the name of this aggregation.
    171      *
    172      * @return the name of this aggregation exactly as it appears in the
    173      * D program minus the preceding {@code @}, or an empty string if
    174      * the aggregation is unnamed, for example:
    175      * <pre>		{@code @[execname] = count();}</pre>
    176      */
    177     public String
    178     getName()
    179     {
    180 	return name;
    181     }
    182 
    183     /**
    184      * Gets the D compiler-generated ID of this aggregation.
    185      *
    186      * @return the D compiler-generated ID
    187      */
    188     public long
    189     getID()
    190     {
    191 	return id;
    192     }
    193 
    194     /**
    195      * Gets an unordered list of this aggregation's records. The list is
    196      * sortable using {@link java.util.Collections#sort(List list,
    197      * Comparator c)} with any user-defined ordering. Modifying the
    198      * returned list has no effect on this aggregation. Supports XML
    199      * persistence.
    200      *
    201      * @return a newly created list that copies this aggregation's
    202      * records by reference in no particular order
    203      * @see Aggregate#getRecords()
    204      * @see Aggregate#getOrderedRecords()
    205      */
    206     public List <AggregationRecord>
    207     getRecords()
    208     {
    209 	List <AggregationRecord> list =
    210 		new ArrayList <AggregationRecord> (map.values());
    211 	return list;
    212     }
    213 
    214     /**
    215      * Package level access, called by Aggregate and PrintaRecord.
    216      *
    217      * @throws IllegalArgumentException if this aggregation already
    218      * contains a record with the same tuple key as the given record
    219      */
    220     void
    221     addRecord(AggregationRecord record)
    222     {
    223 	Tuple key = record.getTuple();
    224 	if (map.put(key, record) != null) {
    225 	    throw new IllegalArgumentException("already contains a record " +
    226 		    "with tuple " + key);
    227 	}
    228     }
    229 
    230     /**
    231      * Gets a read-only {@code Map} view of this aggregation.
    232      *
    233      * @return a read-only {@code Map} view of this aggregation
    234      */
    235     public Map <Tuple, AggregationRecord>
    236     asMap()
    237     {
    238 	return Collections. <Tuple, AggregationRecord> unmodifiableMap(map);
    239     }
    240 
    241     /**
    242      * Compares the specified object with this aggregation for equality.
    243      * Defines equality as having equal names and equal records.
    244      *
    245      * @return {@code true} if and only if the specified object is an
    246      * {@code Aggregation} with the same name as this aggregation and
    247      * the {@code Map} views of both aggregations returned by {@link
    248      * #asMap()} are equal as defined by {@link
    249      * AbstractMap#equals(Object o) AbstractMap.equals()}
    250      */
    251     @Override
    252     public boolean
    253     equals(Object o)
    254     {
    255 	if (o instanceof Aggregation) {
    256 	    Aggregation a = (Aggregation)o;
    257 	    return (name.equals(a.name) &&
    258 		    (map.equals(a.map))); // same mappings
    259 	}
    260 	return false;
    261     }
    262 
    263     /**
    264      * Overridden to ensure that equal aggregations have equal hash
    265      * codes.
    266      */
    267     @Override
    268     public int
    269     hashCode()
    270     {
    271 	int hash = 17;
    272 	hash = (37 * hash) + name.hashCode();
    273 	hash = (37 * hash) + map.hashCode();
    274 	return hash;
    275     }
    276 
    277     /**
    278      * Gets the record associated with the given key, or the singleton
    279      * record of an aggregation declared without square brackets if
    280      * {@code key} is {@code null} or empty.
    281      *
    282      * @param key  The record key, or an empty tuple (see {@link
    283      * Tuple#EMPTY}) to obtain the value from a <i>singleton</i> (a
    284      * non-keyed instance with only a single value) generated from a
    285      * DTrace aggregation declarated without square brackets, for
    286      * example:
    287      * <pre>		{@code @a = count();}</pre>
    288      * @return the record associated with the given key, or {@code null}
    289      * if no record in this aggregation is associated with the given key
    290      */
    291     public AggregationRecord
    292     getRecord(Tuple key)
    293     {
    294 	if (key == null) {
    295 	    key = Tuple.EMPTY;
    296 	}
    297 	return map.get(key);
    298     }
    299 
    300     /**
    301      * Serialize this {@code Aggregation} instance.
    302      *
    303      * @serialData Serialized fields are emitted, followed by a {@link
    304      * java.util.List} of {@link AggregationRecord} instances.
    305      */
    306     private void
    307     writeObject(ObjectOutputStream s) throws IOException
    308     {
    309 	s.defaultWriteObject();
    310 	s.writeObject(getRecords());
    311     }
    312 
    313     @SuppressWarnings("unchecked")
    314     private void
    315     readObject(ObjectInputStream s)
    316             throws IOException, ClassNotFoundException
    317     {
    318 	s.defaultReadObject();
    319 	// cannot cast to parametric type without compiler warning
    320 	List <AggregationRecord> records = (List)s.readObject();
    321 	// load serialized form into private map as a defensive copy
    322 	mapRecords(records);
    323 	// Check class invariants (only after defensive copy)
    324 	name = Aggregate.filterUnnamedAggregationName(name);
    325 	try {
    326 	    validate();
    327 	} catch (Exception e) {
    328 	    InvalidObjectException x = new InvalidObjectException(
    329 		    e.getMessage());
    330 	    x.initCause(e);
    331 	    throw x;
    332 	}
    333     }
    334 
    335     /**
    336      * Gets a string representation of this aggregation useful for
    337      * logging and not intended for display.  The exact details of the
    338      * representation are unspecified and subject to change, but the
    339      * following format may be regarded as typical:
    340      * <pre><code>
    341      * class-name[property1 = value1, property2 = value2]
    342      * </code></pre>
    343      */
    344     @Override
    345     public String
    346     toString()
    347     {
    348 	StringBuilder buf = new StringBuilder();
    349 	buf.append(Aggregation.class.getName());
    350 	buf.append("[name = ");
    351 	buf.append(name);
    352 	buf.append(", id = ");
    353 	buf.append(id);
    354 	buf.append(", records = ");
    355 	List <AggregationRecord> recordList = getRecords();
    356 	// Sort by tuple so that equal aggregations have equal strings
    357 	Collections.sort(recordList, new Comparator <AggregationRecord> () {
    358 	    public int compare(AggregationRecord r1, AggregationRecord r2) {
    359 		Tuple t1 = r1.getTuple();
    360 		Tuple t2 = r2.getTuple();
    361 		return t1.compareTo(t2);
    362 	    }
    363 	});
    364 	buf.append('[');
    365 	boolean first = true;
    366 	for (AggregationRecord record : recordList) {
    367 	    if (first) {
    368 		first = false;
    369 	    } else {
    370 		buf.append(", ");
    371 	    }
    372 	    buf.append(record);
    373 	}
    374 	buf.append(']');
    375 	return buf.toString();
    376     }
    377 }
    378