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 Aggregate(long snaptimeNanos)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 Aggregate(long snaptimeNanos, Collection <Aggregation> aggregations)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 mapAggregations(Collection <Aggregation> aggregations)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 validate()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 getSnaptime()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> getAggregations()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 getAggregation(String name)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> getRecords()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 <AggregationRecord> () { 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> getOrderedRecords()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 filterUnnamedAggregationName(String name)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> asMap()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 addRecord(String aggregationName, long aggid, AggregationRecord rec)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 writeObject(ObjectOutputStream s)321 writeObject(ObjectOutputStream s) throws IOException 322 { 323 s.defaultWriteObject(); 324 s.writeObject(getAggregations()); 325 } 326 327 @SuppressWarnings("unchecked") 328 private void readObject(ObjectInputStream s)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 toString()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