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