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