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 2007 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 
71     /**
72      * Called by native code.
73      */
74     private
75     Aggregate(long snaptimeNanos)
76     {
77 	snaptime = snaptimeNanos;
78 	map = new HashMap <String, Aggregation> ();
79     }
80 
81     /**
82      * Creates an aggregate with the given snaptime and aggregations.
83      * Supports XML persistence.
84      *
85      * @param snaptimeNanos nanosecond timestamp when this aggregate was
86      * snapped
87      * @param aggregations unordered collection of aggregations
88      * belonging to this aggregate
89      * @throws NullPointerException if the given collection of
90      * aggregations is {@code null}
91      */
92     public
93     Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)
94     {
95 	snaptime = snaptimeNanos;
96 	mapAggregations(aggregations);
97     }
98 
99     // assumes map is not yet created
100     private void
101     mapAggregations(Collection <Aggregation> aggregations)
102     {
103 	int capacity = (int)(((float)aggregations.size() * 3.0f) / 2.0f);
104 	// avoid rehashing and optimize lookup; will never be modified
105 	map = new HashMap <String, Aggregation> (capacity, 1.0f);
106 	for (Aggregation a : aggregations) {
107 	    map.put(a.getName(), a);
108 	}
109     }
110 
111     /**
112      * Gets the nanosecond timestamp of this aggregate snapshot.
113      *
114      * @return nanosecond timestamp of this aggregate snapshot
115      */
116     public long
117     getSnaptime()
118     {
119 	return snaptime;
120     }
121 
122     /**
123      * Gets an unordered list of all aggregations in this aggregate
124      * snapshot.  The list is easily sortable using {@link
125      * java.util.Collections#sort(List list, Comparator c)} provided any
126      * user-defined ordering.  Modifying the returned list has no effect
127      * on this aggregate.  Supports XML persistence.
128      *
129      * @return modifiable unordered list of all aggregations in this
130      * aggregate snapshot; list is non-null and possibly empty
131      */
132     public List <Aggregation>
133     getAggregations()
134     {
135 	// Must return an instance of a public, mutable class in order
136 	// to support XML persistence.
137 	List <Aggregation> list = new ArrayList <Aggregation> (map.size());
138 	list.addAll(map.values());
139 	return list;
140     }
141 
142     /**
143      * Gets the aggregation with the given name if it exists in this
144      * aggregate snapshot.
145      *
146      * @param name  the name of the desired aggregation, or empty string
147      * to request the unnamed aggregation.  In D, the unnamed
148      * aggregation is used anytime a name does not follow the
149      * aggregation symbol '{@code @}', for example:
150      * <pre>		{@code @ = count();}</pre> as opposed to
151      * <pre>		{@code @counts = count()}</pre> resulting in an
152      * {@code Aggregation} with the name "counts".
153      *
154      * @return {@code null} if no aggregation by the given name exists
155      * in this aggregate
156      * @see Aggregation#getName()
157      */
158     public Aggregation
159     getAggregation(String name)
160     {
161 	// This was decided March 18, 2005 in a meeting with the DTrace
162 	// team that calling getAggregation() with underbar should
163 	// return the unnamed aggregation (same as calling with empty
164 	// string).  Underbar is used to identify the unnamed
165 	// aggregation in libdtrace; in the jave API it is identifed by
166 	// the empty string.  The API never presents underbar but
167 	// accepts it as input (just converts underbar to empty string
168 	// everywhere it sees it).
169 	name = Aggregate.filterUnnamedAggregationName(name);
170 	return map.get(name);
171     }
172 
173     /**
174      * In the native DTrace library, the unnamed aggregation {@code @}
175      * is given the name {@code _} (underbar).  The Java DTrace API does
176      * not expose this implementation detail but instead identifies the
177      * unnamed aggregation with the empty string.  Here we convert the
178      * name of the unnamed aggregation at the earliest opportunity.
179      * <p>
180      * Package level access.  Called by this class and PrintaRecord when
181      * adding the Aggregation abstraction on top of native aggregation
182      * records.
183      */
184     static String
185     filterUnnamedAggregationName(String name)
186     {
187 	if ((name != null) && name.equals("_")) {
188 	    return "";
189 	}
190 	return name;
191     }
192 
193     /**
194      * Gets a read-only {@code Map} view of this aggregate.
195      *
196      * @return a read-only {@code Map} view of this aggregate keyed by
197      * aggregation name
198      */
199     public Map <String, Aggregation>
200     asMap()
201     {
202 	return Collections. <String, Aggregation> unmodifiableMap(map);
203     }
204 
205     /**
206      * Called by native code.
207      *
208      * @throws IllegalStateException if the aggregation with the given
209      * name already has a record with the same tuple key as the given
210      * record.
211      */
212     private void
213     addRecord(String aggregationName, long aggid, AggregationRecord rec)
214     {
215 	aggregationName = Aggregate.filterUnnamedAggregationName(
216 		aggregationName);
217 	Aggregation aggregation = getAggregation(aggregationName);
218 	if (aggregation == null) {
219 	    aggregation = new Aggregation(aggregationName, aggid);
220 	    map.put(aggregationName, aggregation);
221 	}
222 	aggregation.addRecord(rec);
223     }
224 
225     /**
226      * Serialize this {@code Aggregate} instance.
227      *
228      * @serialData Serialized fields are emitted, followed by a {@link
229      * java.util.List} of {@link Aggregation} instances.
230      */
231     private void
232     writeObject(ObjectOutputStream s) throws IOException
233     {
234 	s.defaultWriteObject();
235 	s.writeObject(getAggregations());
236     }
237 
238     @SuppressWarnings("unchecked")
239     private void
240     readObject(ObjectInputStream s)
241             throws IOException, ClassNotFoundException
242     {
243 	s.defaultReadObject();
244 	// cannot cast to parametric type without compiler warning
245 	List <Aggregation> aggregations = (List)s.readObject();
246 	// load serialized form into private map as a defensive copy
247 	mapAggregations(aggregations);
248 	// check class invariants after defensive copy
249     }
250 
251     /**
252      * Gets a string representation of this aggregate snapshot useful
253      * for logging and not intended for display.  The exact details of
254      * the representation are unspecified and subject to change, but the
255      * following format may be regarded as typical:
256      * <pre><code>
257      * class-name[property1 = value1, property2 = value2]
258      * </code></pre>
259      */
260     public String
261     toString()
262     {
263 	StringBuilder buf = new StringBuilder();
264 	buf.append(Aggregate.class.getName());
265 	buf.append("[snaptime = ");
266 	buf.append(snaptime);
267 	buf.append(", aggregations = ");
268 	List <Aggregation> a = getAggregations();
269 	Collections.sort(a, new Comparator <Aggregation> () {
270 	    public int compare(Aggregation a1, Aggregation a2) {
271 		return a1.getName().compareTo(a2.getName());
272 	    }
273 	});
274 	buf.append(Arrays.toString(a.toArray()));
275 	buf.append(']');
276 	return buf.toString();
277     }
278 }
279