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 snapshot of a DTrace aggregation. The name of an {@code 36 * Aggregation} instance matches the source declaration, for example 37 * <pre> {@code @a[execname] = count();}</pre> 38 * results in an {@code Aggregation} named "a" (the name does not 39 * include the preceding {@code @}). For convenience, a single 40 * aggregation can remain unnamed (multiple aggregations in the same D 41 * program need distinct names). The unnamed aggregation results in an 42 * {@code Aggregation} instance whose name is the empty string, for 43 * example 44 * <pre> {@code @[execname] = count();}</pre> 45 * An aggregation can list more than one variable in square brackets in 46 * order to accumulate a value for each unique combination, or {@link 47 * Tuple}. Each tuple instance is associated with its accumulated 48 * {@link AggregationValue} in an {@link AggregationRecord}. For 49 * example 50 * <pre> {@code @counts[execname, probefunc, cpu] = count();}</pre> 51 * results in an {@code Aggregation} named "counts" containing records 52 * each pairing a {@link CountValue} to a three-element {@code Tuple}. 53 * It is also possible to omit the square brackets, for example 54 * <pre> {@code @a = count();}</pre> 55 * results in an {@code Aggregation} named "a" with only a single record 56 * keyed to the empty tuple ({@link Tuple#EMPTY}). 57 * <p> 58 * For more information, see the <a 59 * href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidlh7?a=view> 60 * <b>Aggregations</b></a> chapter of the <i>Solaris Dynamic Tracing 61 * Guide</i>. Also, the <a 62 * href=http://docs.sun.com/app/docs/doc/817-6223/6mlkidlfv?a=view> 63 * <b>Built-in Variables</b></a> section of the <b>Variables</b> chapter 64 * describes variables like {@code execname}, {@code probefunc}, and 65 * {@code cpu} useful for aggregating. 66 * <p> 67 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 68 * 69 * @see Aggregate 70 * @see PrintaRecord 71 * 72 * @author Tom Erickson 73 */ 74 public final class Aggregation implements Serializable { 75 static final long serialVersionUID = 2340811719178724026L; 76 77 static { 78 try { 79 BeanInfo info = Introspector.getBeanInfo(Aggregation.class); 80 PersistenceDelegate persistenceDelegate = 81 new DefaultPersistenceDelegate( 82 new String[] {"name", "ID", "records"}) 83 { 84 @Override 85 protected boolean 86 mutatesTo(Object oldInstance, Object newInstance) 87 { 88 return ((newInstance != null) && (oldInstance != null) && 89 (oldInstance.getClass() == newInstance.getClass())); 90 } 91 }; 92 BeanDescriptor d = info.getBeanDescriptor(); 93 d.setValue("persistenceDelegate", persistenceDelegate); 94 } catch (IntrospectionException e) { 95 e.printStackTrace(); 96 } 97 } 98 99 /** @serial */ 100 private String name; 101 /** @serial */ 102 private final long id; 103 private transient Map <Tuple, AggregationRecord> map; 104 105 /** 106 * Package-level access, called by Aggregate 107 */ Aggregation(String aggregationName, long aggregationID)108 Aggregation(String aggregationName, long aggregationID) 109 { 110 name = Aggregate.filterUnnamedAggregationName(aggregationName); 111 id = aggregationID; 112 map = new HashMap <Tuple, AggregationRecord> (); 113 } 114 115 /** 116 * Creates an aggregation with the given name, ID, and records. 117 * Supports XML persistence. 118 * 119 * @param aggregationName the name of this aggregation, empty string 120 * if this aggregation is unnamed 121 * @param aggregationID ID generated from a sequence by the native 122 * DTrace library 123 * @param aggregationRecords unordered collection of records 124 * belonging to this aggregation 125 * @throws NullPointerException if the specified name or list of 126 * records is {@code null} 127 * @throws IllegalArgumentException if any record has an empty 128 * tuple, unless it is the only record in the given collection (only 129 * a singleton generated by an aggregation without square brackets 130 * uses {@link Tuple#EMPTY} as a key) 131 * @see #getRecord(Tuple key) 132 */ 133 public Aggregation(String aggregationName, long aggregationID, Collection <AggregationRecord> aggregationRecords)134 Aggregation(String aggregationName, long aggregationID, 135 Collection <AggregationRecord> aggregationRecords) 136 { 137 name = Aggregate.filterUnnamedAggregationName(aggregationName); 138 id = aggregationID; 139 mapRecords(aggregationRecords); 140 validate(); 141 } 142 143 // assumes map is not yet created 144 private void mapRecords(Collection <AggregationRecord> records)145 mapRecords(Collection <AggregationRecord> records) 146 { 147 int capacity = (int)(((float)records.size() * 3.0f) / 2.0f); 148 // avoid rehashing and optimize lookup; will never be modified 149 map = new HashMap <Tuple, AggregationRecord> (capacity, 1.0f); 150 for (AggregationRecord record : records) { 151 map.put(record.getTuple(), record); 152 } 153 } 154 155 private final void validate()156 validate() 157 { 158 if (name == null) { 159 throw new NullPointerException("name is null"); 160 } 161 for (AggregationRecord r : map.values()) { 162 if ((r.getTuple().size() == 0) && (map.size() > 1)) { 163 throw new IllegalArgumentException("empty tuple " + 164 "allowed only in singleton aggregation"); 165 } 166 } 167 } 168 169 /** 170 * Gets the name of this aggregation. 171 * 172 * @return the name of this aggregation exactly as it appears in the 173 * D program minus the preceding {@code @}, or an empty string if 174 * the aggregation is unnamed, for example: 175 * <pre> {@code @[execname] = count();}</pre> 176 */ 177 public String getName()178 getName() 179 { 180 return name; 181 } 182 183 /** 184 * Gets the D compiler-generated ID of this aggregation. 185 * 186 * @return the D compiler-generated ID 187 */ 188 public long getID()189 getID() 190 { 191 return id; 192 } 193 194 /** 195 * Gets an unordered list of this aggregation's records. The list is 196 * sortable using {@link java.util.Collections#sort(List list, 197 * Comparator c)} with any user-defined ordering. Modifying the 198 * returned list has no effect on this aggregation. Supports XML 199 * persistence. 200 * 201 * @return a newly created list that copies this aggregation's 202 * records by reference in no particular order 203 * @see Aggregate#getRecords() 204 * @see Aggregate#getOrderedRecords() 205 */ 206 public List <AggregationRecord> getRecords()207 getRecords() 208 { 209 List <AggregationRecord> list = 210 new ArrayList <AggregationRecord> (map.values()); 211 return list; 212 } 213 214 /** 215 * Package level access, called by Aggregate and PrintaRecord. 216 * 217 * @throws IllegalArgumentException if this aggregation already 218 * contains a record with the same tuple key as the given record 219 */ 220 void addRecord(AggregationRecord record)221 addRecord(AggregationRecord record) 222 { 223 Tuple key = record.getTuple(); 224 if (map.put(key, record) != null) { 225 throw new IllegalArgumentException("already contains a record " + 226 "with tuple " + key); 227 } 228 } 229 230 /** 231 * Gets a read-only {@code Map} view of this aggregation. 232 * 233 * @return a read-only {@code Map} view of this aggregation 234 */ 235 public Map <Tuple, AggregationRecord> asMap()236 asMap() 237 { 238 return Collections. <Tuple, AggregationRecord> unmodifiableMap(map); 239 } 240 241 /** 242 * Compares the specified object with this aggregation for equality. 243 * Defines equality as having equal names and equal records. 244 * 245 * @return {@code true} if and only if the specified object is an 246 * {@code Aggregation} with the same name as this aggregation and 247 * the {@code Map} views of both aggregations returned by {@link 248 * #asMap()} are equal as defined by {@link 249 * AbstractMap#equals(Object o) AbstractMap.equals()} 250 */ 251 @Override 252 public boolean equals(Object o)253 equals(Object o) 254 { 255 if (o instanceof Aggregation) { 256 Aggregation a = (Aggregation)o; 257 return (name.equals(a.name) && 258 (map.equals(a.map))); // same mappings 259 } 260 return false; 261 } 262 263 /** 264 * Overridden to ensure that equal aggregations have equal hash 265 * codes. 266 */ 267 @Override 268 public int hashCode()269 hashCode() 270 { 271 int hash = 17; 272 hash = (37 * hash) + name.hashCode(); 273 hash = (37 * hash) + map.hashCode(); 274 return hash; 275 } 276 277 /** 278 * Gets the record associated with the given key, or the singleton 279 * record of an aggregation declared without square brackets if 280 * {@code key} is {@code null} or empty. 281 * 282 * @param key The record key, or an empty tuple (see {@link 283 * Tuple#EMPTY}) to obtain the value from a <i>singleton</i> (a 284 * non-keyed instance with only a single value) generated from a 285 * DTrace aggregation declarated without square brackets, for 286 * example: 287 * <pre> {@code @a = count();}</pre> 288 * @return the record associated with the given key, or {@code null} 289 * if no record in this aggregation is associated with the given key 290 */ 291 public AggregationRecord getRecord(Tuple key)292 getRecord(Tuple key) 293 { 294 if (key == null) { 295 key = Tuple.EMPTY; 296 } 297 return map.get(key); 298 } 299 300 /** 301 * Serialize this {@code Aggregation} instance. 302 * 303 * @serialData Serialized fields are emitted, followed by a {@link 304 * java.util.List} of {@link AggregationRecord} instances. 305 */ 306 private void writeObject(ObjectOutputStream s)307 writeObject(ObjectOutputStream s) throws IOException 308 { 309 s.defaultWriteObject(); 310 s.writeObject(getRecords()); 311 } 312 313 @SuppressWarnings("unchecked") 314 private void readObject(ObjectInputStream s)315 readObject(ObjectInputStream s) 316 throws IOException, ClassNotFoundException 317 { 318 s.defaultReadObject(); 319 // cannot cast to parametric type without compiler warning 320 List <AggregationRecord> records = (List)s.readObject(); 321 // load serialized form into private map as a defensive copy 322 mapRecords(records); 323 // Check class invariants (only after defensive copy) 324 name = Aggregate.filterUnnamedAggregationName(name); 325 try { 326 validate(); 327 } catch (Exception e) { 328 InvalidObjectException x = new InvalidObjectException( 329 e.getMessage()); 330 x.initCause(e); 331 throw x; 332 } 333 } 334 335 /** 336 * Gets a string representation of this aggregation useful for 337 * logging and not intended for display. The exact details of the 338 * representation are unspecified and subject to change, but the 339 * following format may be regarded as typical: 340 * <pre><code> 341 * class-name[property1 = value1, property2 = value2] 342 * </code></pre> 343 */ 344 @Override 345 public String toString()346 toString() 347 { 348 StringBuilder buf = new StringBuilder(); 349 buf.append(Aggregation.class.getName()); 350 buf.append("[name = "); 351 buf.append(name); 352 buf.append(", id = "); 353 buf.append(id); 354 buf.append(", records = "); 355 List <AggregationRecord> recordList = getRecords(); 356 // Sort by tuple so that equal aggregations have equal strings 357 Collections.sort(recordList, new Comparator <AggregationRecord> () { 358 public int compare(AggregationRecord r1, AggregationRecord r2) { 359 Tuple t1 = r1.getTuple(); 360 Tuple t2 = r2.getTuple(); 361 return t1.compareTo(t2); 362 } 363 }); 364 buf.append('['); 365 boolean first = true; 366 for (AggregationRecord record : recordList) { 367 if (first) { 368 first = false; 369 } else { 370 buf.append(", "); 371 } 372 buf.append(record); 373 } 374 buf.append(']'); 375 return buf.toString(); 376 } 377 } 378