1fb3fb4f3Stomee /*
2fb3fb4f3Stomee  * CDDL HEADER START
3fb3fb4f3Stomee  *
4fb3fb4f3Stomee  * The contents of this file are subject to the terms of the
5fb3fb4f3Stomee  * Common Development and Distribution License (the "License").
6fb3fb4f3Stomee  * You may not use this file except in compliance with the License.
7fb3fb4f3Stomee  *
8fb3fb4f3Stomee  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9fb3fb4f3Stomee  * or http://www.opensolaris.org/os/licensing.
10fb3fb4f3Stomee  * See the License for the specific language governing permissions
11fb3fb4f3Stomee  * and limitations under the License.
12fb3fb4f3Stomee  *
13fb3fb4f3Stomee  * When distributing Covered Code, include this CDDL HEADER in each
14fb3fb4f3Stomee  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15fb3fb4f3Stomee  * If applicable, add the following below this CDDL HEADER, with the
16fb3fb4f3Stomee  * fields enclosed by brackets "[]" replaced with your own identifying
17fb3fb4f3Stomee  * information: Portions Copyright [yyyy] [name of copyright owner]
18fb3fb4f3Stomee  *
19fb3fb4f3Stomee  * CDDL HEADER END
20fb3fb4f3Stomee  */
21fb3fb4f3Stomee 
22fb3fb4f3Stomee /*
23e77b06d2Stomee  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24fb3fb4f3Stomee  * Use is subject to license terms.
25fb3fb4f3Stomee  */
26fb3fb4f3Stomee package org.opensolaris.os.dtrace;
27fb3fb4f3Stomee 
28fb3fb4f3Stomee import java.util.*;
29fb3fb4f3Stomee import java.beans.*;
30fb3fb4f3Stomee import java.io.*;
31fb3fb4f3Stomee 
32fb3fb4f3Stomee /**
33fb3fb4f3Stomee  * A consistent snapshot of all aggregations requested by a single
34fb3fb4f3Stomee  * {@link Consumer}.
35fb3fb4f3Stomee  * <p>
36fb3fb4f3Stomee  * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
37fb3fb4f3Stomee  *
38fb3fb4f3Stomee  * @see Consumer#getAggregate()
39fb3fb4f3Stomee  *
40fb3fb4f3Stomee  * @author Tom Erickson
41fb3fb4f3Stomee  */
42fb3fb4f3Stomee public final class Aggregate implements Serializable
43fb3fb4f3Stomee {
44fb3fb4f3Stomee     static final long serialVersionUID = 3180340417154076628L;
45fb3fb4f3Stomee 
46fb3fb4f3Stomee     static {
47fb3fb4f3Stomee 	try {
48fb3fb4f3Stomee 	    BeanInfo info = Introspector.getBeanInfo(Aggregate.class);
49fb3fb4f3Stomee 	    PersistenceDelegate persistenceDelegate =
50fb3fb4f3Stomee 		    new DefaultPersistenceDelegate(
51fb3fb4f3Stomee 		    new String[] {"snaptime", "aggregations"});
52fb3fb4f3Stomee 	    BeanDescriptor d = info.getBeanDescriptor();
53fb3fb4f3Stomee 	    d.setValue("persistenceDelegate", persistenceDelegate);
54fb3fb4f3Stomee 	} catch (IntrospectionException e) {
55fb3fb4f3Stomee 	    System.out.println(e);
56fb3fb4f3Stomee 	}
57fb3fb4f3Stomee     }
58fb3fb4f3Stomee 
59fb3fb4f3Stomee     /** @serial */
60fb3fb4f3Stomee     private final long snaptime;
61fb3fb4f3Stomee 
62fb3fb4f3Stomee     // Map must not have same name as named PersistenceDelegate property
63fb3fb4f3Stomee     // ("aggregations"), otherwise it gets confused for a bean property
64fb3fb4f3Stomee     // and XMLDecoder calls the constructor with a Map instead of the
65fb3fb4f3Stomee     // value of the getAggregations() method.
66fb3fb4f3Stomee 
67fb3fb4f3Stomee     private transient Map <String, Aggregation> map;
68e77b06d2Stomee     private transient int recordSequence;
69fb3fb4f3Stomee 
70fb3fb4f3Stomee     /**
71fb3fb4f3Stomee      * Called by native code.
72fb3fb4f3Stomee      */
73fb3fb4f3Stomee     private
Aggregate(long snaptimeNanos)74fb3fb4f3Stomee     Aggregate(long snaptimeNanos)
75fb3fb4f3Stomee     {
76fb3fb4f3Stomee 	snaptime = snaptimeNanos;
77fb3fb4f3Stomee 	map = new HashMap <String, Aggregation> ();
78fb3fb4f3Stomee     }
79fb3fb4f3Stomee 
80fb3fb4f3Stomee     /**
81fb3fb4f3Stomee      * Creates an aggregate with the given snaptime and aggregations.
82fb3fb4f3Stomee      * Supports XML persistence.
83fb3fb4f3Stomee      *
84fb3fb4f3Stomee      * @param snaptimeNanos nanosecond timestamp when this aggregate was
85fb3fb4f3Stomee      * snapped
86fb3fb4f3Stomee      * @param aggregations unordered collection of aggregations
87fb3fb4f3Stomee      * belonging to this aggregate
88fb3fb4f3Stomee      * @throws NullPointerException if the given collection of
89fb3fb4f3Stomee      * aggregations is {@code null}
90e77b06d2Stomee      * @throws IllegalArgumentException if the record ordinals of the
91e77b06d2Stomee      * given aggregations are invalid
92fb3fb4f3Stomee      */
93fb3fb4f3Stomee     public
Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)94fb3fb4f3Stomee     Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)
95fb3fb4f3Stomee     {
96fb3fb4f3Stomee 	snaptime = snaptimeNanos;
97fb3fb4f3Stomee 	mapAggregations(aggregations);
98e77b06d2Stomee 	validate();
99fb3fb4f3Stomee     }
100fb3fb4f3Stomee 
101fb3fb4f3Stomee     // assumes map is not yet created
102fb3fb4f3Stomee     private void
mapAggregations(Collection <Aggregation> aggregations)103fb3fb4f3Stomee     mapAggregations(Collection <Aggregation> aggregations)
104fb3fb4f3Stomee     {
105fb3fb4f3Stomee 	int capacity = (int)(((float)aggregations.size() * 3.0f) / 2.0f);
106fb3fb4f3Stomee 	// avoid rehashing and optimize lookup; will never be modified
107fb3fb4f3Stomee 	map = new HashMap <String, Aggregation> (capacity, 1.0f);
108fb3fb4f3Stomee 	for (Aggregation a : aggregations) {
109fb3fb4f3Stomee 	    map.put(a.getName(), a);
110e77b06d2Stomee 	    recordSequence += a.asMap().size();
111e77b06d2Stomee 	}
112e77b06d2Stomee     }
113e77b06d2Stomee 
114e77b06d2Stomee     private void
validate()115e77b06d2Stomee     validate()
116e77b06d2Stomee     {
117e77b06d2Stomee 	int capacity = (int)(((float)recordSequence * 3.0f) / 2.0f);
118e77b06d2Stomee 	Set <Integer> ordinals = new HashSet <Integer> (capacity, 1.0f);
119e77b06d2Stomee 	int ordinal, max = 0;
120e77b06d2Stomee 	for (Aggregation a : map.values()) {
121e77b06d2Stomee 	    for (AggregationRecord r : a.asMap().values()) {
122e77b06d2Stomee 		// Allow all ordinals to be zero for backward
123e77b06d2Stomee 		// compatibility (allows XML decoding of aggregates that
124e77b06d2Stomee 		// were encoded before the ordinal property was added).
125e77b06d2Stomee 		if (!ordinals.add(ordinal = r.getOrdinal()) && (ordinal > 0)) {
126e77b06d2Stomee 		    throw new IllegalArgumentException(
127e77b06d2Stomee 			    "duplicate record ordinal: " + ordinal);
128e77b06d2Stomee 		}
129e77b06d2Stomee 		if (ordinal > max) {
130e77b06d2Stomee 		    max = ordinal;
131e77b06d2Stomee 		}
132e77b06d2Stomee 	    }
133e77b06d2Stomee 	}
134e77b06d2Stomee 	if ((max > 0) && (max != (recordSequence - 1))) {
135e77b06d2Stomee 	    throw new IllegalArgumentException(
136e77b06d2Stomee 		    "The maximum record ordinal (" + max + ") does not " +
137e77b06d2Stomee 		    "equal the number of records (" + recordSequence +
138e77b06d2Stomee 		    ") minus one.");
139fb3fb4f3Stomee 	}
140fb3fb4f3Stomee     }
141fb3fb4f3Stomee 
142fb3fb4f3Stomee     /**
143fb3fb4f3Stomee      * Gets the nanosecond timestamp of this aggregate snapshot.
144fb3fb4f3Stomee      *
145fb3fb4f3Stomee      * @return nanosecond timestamp of this aggregate snapshot
146fb3fb4f3Stomee      */
147fb3fb4f3Stomee     public long
getSnaptime()148fb3fb4f3Stomee     getSnaptime()
149fb3fb4f3Stomee     {
150fb3fb4f3Stomee 	return snaptime;
151fb3fb4f3Stomee     }
152fb3fb4f3Stomee 
153fb3fb4f3Stomee     /**
154fb3fb4f3Stomee      * Gets an unordered list of all aggregations in this aggregate
155fb3fb4f3Stomee      * snapshot.  The list is easily sortable using {@link
156fb3fb4f3Stomee      * java.util.Collections#sort(List list, Comparator c)} provided any
157fb3fb4f3Stomee      * user-defined ordering.  Modifying the returned list has no effect
158fb3fb4f3Stomee      * on this aggregate.  Supports XML persistence.
159fb3fb4f3Stomee      *
160fb3fb4f3Stomee      * @return modifiable unordered list of all aggregations in this
161fb3fb4f3Stomee      * aggregate snapshot; list is non-null and possibly empty
162fb3fb4f3Stomee      */
163fb3fb4f3Stomee     public List <Aggregation>
getAggregations()164fb3fb4f3Stomee     getAggregations()
165fb3fb4f3Stomee     {
166fb3fb4f3Stomee 	// Must return an instance of a public, mutable class in order
167fb3fb4f3Stomee 	// to support XML persistence.
168fb3fb4f3Stomee 	List <Aggregation> list = new ArrayList <Aggregation> (map.size());
169fb3fb4f3Stomee 	list.addAll(map.values());
170fb3fb4f3Stomee 	return list;
171fb3fb4f3Stomee     }
172fb3fb4f3Stomee 
173fb3fb4f3Stomee     /**
174fb3fb4f3Stomee      * Gets the aggregation with the given name if it exists in this
175fb3fb4f3Stomee      * aggregate snapshot.
176fb3fb4f3Stomee      *
177fb3fb4f3Stomee      * @param name  the name of the desired aggregation, or empty string
178fb3fb4f3Stomee      * to request the unnamed aggregation.  In D, the unnamed
179fb3fb4f3Stomee      * aggregation is used anytime a name does not follow the
180fb3fb4f3Stomee      * aggregation symbol '{@code @}', for example:
181fb3fb4f3Stomee      * <pre>		{@code @ = count();}</pre> as opposed to
182fb3fb4f3Stomee      * <pre>		{@code @counts = count()}</pre> resulting in an
183fb3fb4f3Stomee      * {@code Aggregation} with the name "counts".
184fb3fb4f3Stomee      *
185fb3fb4f3Stomee      * @return {@code null} if no aggregation by the given name exists
186fb3fb4f3Stomee      * in this aggregate
187fb3fb4f3Stomee      * @see Aggregation#getName()
188fb3fb4f3Stomee      */
189fb3fb4f3Stomee     public Aggregation
getAggregation(String name)190fb3fb4f3Stomee     getAggregation(String name)
191fb3fb4f3Stomee     {
192fb3fb4f3Stomee 	// This was decided March 18, 2005 in a meeting with the DTrace
193fb3fb4f3Stomee 	// team that calling getAggregation() with underbar should
194fb3fb4f3Stomee 	// return the unnamed aggregation (same as calling with empty
195fb3fb4f3Stomee 	// string).  Underbar is used to identify the unnamed
1963a931819SPeter Tribble 	// aggregation in libdtrace; in the java API it is identified by
197fb3fb4f3Stomee 	// the empty string.  The API never presents underbar but
198fb3fb4f3Stomee 	// accepts it as input (just converts underbar to empty string
199fb3fb4f3Stomee 	// everywhere it sees it).
200fb3fb4f3Stomee 	name = Aggregate.filterUnnamedAggregationName(name);
201fb3fb4f3Stomee 	return map.get(name);
202fb3fb4f3Stomee     }
203fb3fb4f3Stomee 
204e77b06d2Stomee     /**
205e77b06d2Stomee      * Gets an unordered list of this aggregate's records. The list is
206e77b06d2Stomee      * sortable using {@link java.util.Collections#sort(List list,
207e77b06d2Stomee      * Comparator c)} with any user-defined ordering. Modifying the
208e77b06d2Stomee      * returned list has no effect on this aggregate.
209e77b06d2Stomee      *
210e77b06d2Stomee      * @return a newly created list that copies this aggregate's records
211e77b06d2Stomee      * by reference in no particular order
212e77b06d2Stomee      */
213e77b06d2Stomee     public List <AggregationRecord>
getRecords()214e77b06d2Stomee     getRecords()
215e77b06d2Stomee     {
216e77b06d2Stomee 	List <AggregationRecord> list =
217e77b06d2Stomee 		new ArrayList <AggregationRecord> (recordSequence);
218e77b06d2Stomee 	for (Aggregation a : map.values()) {
219e77b06d2Stomee 	    list.addAll(a.asMap().values());
220e77b06d2Stomee 	}
221e77b06d2Stomee 	return list;
222e77b06d2Stomee     }
223e77b06d2Stomee 
224e77b06d2Stomee     /**
225e77b06d2Stomee      * Gets an ordered list of this aggregate's records sequenced by
226e77b06d2Stomee      * their {@link AggregationRecord#getOrdinal() ordinal} property.
227e77b06d2Stomee      * Note that the unordered list returned by {@link #getRecords()}
228e77b06d2Stomee      * can easily be sorted by any arbitrary criteria, for example by
229e77b06d2Stomee      * key ascending:
230e77b06d2Stomee      * <pre><code>
2313a931819SPeter Tribble      * List &lt;AggregationRecord&gt; records = aggregate.getRecords();
232e77b06d2Stomee      * Collections.sort(records, new Comparator &lt;AggregationRecord&gt; () {
2333a931819SPeter Tribble      *  public int compare(AggregationRecord r1, AggregationRecord r2) {
2343a931819SPeter Tribble      *   return r1.getTuple().compareTo(r2.getTuple());
2353a931819SPeter Tribble      *  }
236e77b06d2Stomee      * });
237e77b06d2Stomee      * </code></pre>
238e77b06d2Stomee      * Use {@code getOrderedRecords()} instead of {@code getRecords()}
239e77b06d2Stomee      * when you want to list records as they would be ordered by {@code
240*bbf21555SRichard Lowe      * dtrace(8)}.
241e77b06d2Stomee      *
242e77b06d2Stomee      * @return a newly created list of this aggregate's records
243e77b06d2Stomee      * in the order used by the native DTrace library
244e77b06d2Stomee      */
245e77b06d2Stomee     public List <AggregationRecord>
getOrderedRecords()246e77b06d2Stomee     getOrderedRecords()
247e77b06d2Stomee     {
248e77b06d2Stomee 	List <AggregationRecord> list = getRecords();
249e77b06d2Stomee 	Collections.sort(list, new Comparator <AggregationRecord> () {
250e77b06d2Stomee 	    public int compare(AggregationRecord r1, AggregationRecord r2) {
251e77b06d2Stomee 		int n1 = r1.getOrdinal();
252e77b06d2Stomee 		int n2 = r2.getOrdinal();
253e77b06d2Stomee 		return (n1 < n2 ? -1 : (n1 > n2 ? 1 : 0));
254e77b06d2Stomee 	    }
255e77b06d2Stomee 	});
256e77b06d2Stomee 	return list;
257e77b06d2Stomee     }
258e77b06d2Stomee 
259fb3fb4f3Stomee     /**
260fb3fb4f3Stomee      * In the native DTrace library, the unnamed aggregation {@code @}
261fb3fb4f3Stomee      * is given the name {@code _} (underbar).  The Java DTrace API does
262fb3fb4f3Stomee      * not expose this implementation detail but instead identifies the
263fb3fb4f3Stomee      * unnamed aggregation with the empty string.  Here we convert the
264fb3fb4f3Stomee      * name of the unnamed aggregation at the earliest opportunity.
265fb3fb4f3Stomee      * <p>
266fb3fb4f3Stomee      * Package level access.  Called by this class and PrintaRecord when
267fb3fb4f3Stomee      * adding the Aggregation abstraction on top of native aggregation
268fb3fb4f3Stomee      * records.
269fb3fb4f3Stomee      */
270fb3fb4f3Stomee     static String
filterUnnamedAggregationName(String name)271fb3fb4f3Stomee     filterUnnamedAggregationName(String name)
272fb3fb4f3Stomee     {
273fb3fb4f3Stomee 	if ((name != null) && name.equals("_")) {
274fb3fb4f3Stomee 	    return "";
275fb3fb4f3Stomee 	}
276fb3fb4f3Stomee 	return name;
277fb3fb4f3Stomee     }
278fb3fb4f3Stomee 
279fb3fb4f3Stomee     /**
280fb3fb4f3Stomee      * Gets a read-only {@code Map} view of this aggregate.
281fb3fb4f3Stomee      *
282fb3fb4f3Stomee      * @return a read-only {@code Map} view of this aggregate keyed by
283fb3fb4f3Stomee      * aggregation name
284fb3fb4f3Stomee      */
285fb3fb4f3Stomee     public Map <String, Aggregation>
asMap()286fb3fb4f3Stomee     asMap()
287fb3fb4f3Stomee     {
288fb3fb4f3Stomee 	return Collections. <String, Aggregation> unmodifiableMap(map);
289fb3fb4f3Stomee     }
290fb3fb4f3Stomee 
291fb3fb4f3Stomee     /**
292fb3fb4f3Stomee      * Called by native code.
293fb3fb4f3Stomee      *
294fb3fb4f3Stomee      * @throws IllegalStateException if the aggregation with the given
295fb3fb4f3Stomee      * name already has a record with the same tuple key as the given
296fb3fb4f3Stomee      * record.
297fb3fb4f3Stomee      */
298fb3fb4f3Stomee     private void
addRecord(String aggregationName, long aggid, AggregationRecord rec)299fb3fb4f3Stomee     addRecord(String aggregationName, long aggid, AggregationRecord rec)
300fb3fb4f3Stomee     {
301e77b06d2Stomee 	rec.setOrdinal(recordSequence++);
302fb3fb4f3Stomee 	aggregationName = Aggregate.filterUnnamedAggregationName(
303fb3fb4f3Stomee 		aggregationName);
304fb3fb4f3Stomee 	Aggregation aggregation = getAggregation(aggregationName);
305fb3fb4f3Stomee 	if (aggregation == null) {
306fb3fb4f3Stomee 	    aggregation = new Aggregation(aggregationName, aggid);
307fb3fb4f3Stomee 	    map.put(aggregationName, aggregation);
308fb3fb4f3Stomee 	}
309fb3fb4f3Stomee 	aggregation.addRecord(rec);
310fb3fb4f3Stomee     }
311fb3fb4f3Stomee 
312fb3fb4f3Stomee     /**
313fb3fb4f3Stomee      * Serialize this {@code Aggregate} instance.
314fb3fb4f3Stomee      *
315fb3fb4f3Stomee      * @serialData Serialized fields are emitted, followed by a {@link
316fb3fb4f3Stomee      * java.util.List} of {@link Aggregation} instances.
317fb3fb4f3Stomee      */
318fb3fb4f3Stomee     private void
writeObject(ObjectOutputStream s)319fb3fb4f3Stomee     writeObject(ObjectOutputStream s) throws IOException
320fb3fb4f3Stomee     {
321fb3fb4f3Stomee 	s.defaultWriteObject();
322fb3fb4f3Stomee 	s.writeObject(getAggregations());
323fb3fb4f3Stomee     }
324fb3fb4f3Stomee 
325fb3fb4f3Stomee     @SuppressWarnings("unchecked")
326fb3fb4f3Stomee     private void
readObject(ObjectInputStream s)327fb3fb4f3Stomee     readObject(ObjectInputStream s)
328fb3fb4f3Stomee             throws IOException, ClassNotFoundException
329fb3fb4f3Stomee     {
330fb3fb4f3Stomee 	s.defaultReadObject();
331fb3fb4f3Stomee 	// cannot cast to parametric type without compiler warning
332fb3fb4f3Stomee 	List <Aggregation> aggregations = (List)s.readObject();
333fb3fb4f3Stomee 	// load serialized form into private map as a defensive copy
334fb3fb4f3Stomee 	mapAggregations(aggregations);
335fb3fb4f3Stomee 	// check class invariants after defensive copy
336e77b06d2Stomee 	try {
337e77b06d2Stomee 	    validate();
338e77b06d2Stomee 	} catch (Exception e) {
339e77b06d2Stomee 	    InvalidObjectException x = new InvalidObjectException(
340e77b06d2Stomee 		    e.getMessage());
341e77b06d2Stomee 	    x.initCause(e);
342e77b06d2Stomee 	    throw x;
343e77b06d2Stomee 	}
344fb3fb4f3Stomee     }
345fb3fb4f3Stomee 
346fb3fb4f3Stomee     /**
347fb3fb4f3Stomee      * Gets a string representation of this aggregate snapshot useful
348fb3fb4f3Stomee      * for logging and not intended for display.  The exact details of
349fb3fb4f3Stomee      * the representation are unspecified and subject to change, but the
350fb3fb4f3Stomee      * following format may be regarded as typical:
351fb3fb4f3Stomee      * <pre><code>
352fb3fb4f3Stomee      * class-name[property1 = value1, property2 = value2]
353fb3fb4f3Stomee      * </code></pre>
354fb3fb4f3Stomee      */
355fb3fb4f3Stomee     public String
toString()356fb3fb4f3Stomee     toString()
357fb3fb4f3Stomee     {
3584ae67516Stomee 	StringBuilder buf = new StringBuilder();
359fb3fb4f3Stomee 	buf.append(Aggregate.class.getName());
360fb3fb4f3Stomee 	buf.append("[snaptime = ");
361fb3fb4f3Stomee 	buf.append(snaptime);
362fb3fb4f3Stomee 	buf.append(", aggregations = ");
363fb3fb4f3Stomee 	List <Aggregation> a = getAggregations();
364fb3fb4f3Stomee 	Collections.sort(a, new Comparator <Aggregation> () {
365fb3fb4f3Stomee 	    public int compare(Aggregation a1, Aggregation a2) {
366fb3fb4f3Stomee 		return a1.getName().compareTo(a2.getName());
367fb3fb4f3Stomee 	    }
368fb3fb4f3Stomee 	});
369fb3fb4f3Stomee 	buf.append(Arrays.toString(a.toArray()));
370fb3fb4f3Stomee 	buf.append(']');
371fb3fb4f3Stomee 	return buf.toString();
372fb3fb4f3Stomee     }
373fb3fb4f3Stomee }
374