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.io.*; 29 import java.beans.*; 30 import java.util.*; 31 32 /** 33 * A record generated by the DTrace {@code printa()} action. Lists the 34 * aggregations passed to {@code printa()} and records the formatted 35 * output associated with each {@link Tuple}. If multiple aggregations 36 * were passed to the {@code printa()} action that generated this 37 * record, then the DTrace library tabulates the output, using a default 38 * format if no format string was specified. By default, the output 39 * string associated with a given {@code Tuple} includes a value from 40 * each aggregation, or zero wherever an aggregation has no value 41 * associated with that {@code Tuple}. For example, the D statements 42 * <pre><code> 43 * @a[123] = sum(1); 44 * @b[456] = sum(2); 45 * printa(@a, @b, @c); 46 * </code></pre> 47 * produce output for the tuples "123" and "456" similar to the 48 * following: 49 * <pre><code> 50 * 123 1 0 0 51 * 456 0 2 0 52 * </code></pre> 53 * The first column after the tuple contains values from {@code @a}, 54 * the next column contains values from {@code @b}, and the last 55 * column contains zeros because {@code @c} has neither a value 56 * associated with "123" nor a value associated with "456". 57 * <p> 58 * If a format string is passed to {@code printa()}, it may limit the 59 * aggregation data available in this record. For example, if the 60 * format string specifies a value placeholder for only one of two 61 * aggregations passed to {@code printa()}, then the resulting {@code 62 * PrintaRecord} will contain only one {@code Aggregation}. If no value 63 * placeholder is specified, or if the aggregation tuple is not 64 * completely specified, the resulting {@code PrintaRecord} will contain 65 * no aggregation data. However, the formatted output generated by the 66 * DTrace library is available in all cases. For details about 67 * {@code printa()} format strings, see the <a 68 * href=http://dtrace.org/guide/chp-fmt.html#chp-fmt-printa> 69 * <b>{@code printa()}</b></a> section of the <b>Output 70 * Formatting</b> chapter of the <i>Dynamic Tracing Guide</i>. 71 * <p> 72 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 73 * 74 * @author Tom Erickson 75 */ 76 public final class PrintaRecord implements Record, Serializable, 77 Comparable <PrintaRecord> { 78 static final long serialVersionUID = -4174277639915895694L; 79 80 static { 81 try { 82 BeanInfo info = Introspector.getBeanInfo(PrintaRecord.class); 83 PersistenceDelegate persistenceDelegate = 84 new DefaultPersistenceDelegate( 85 new String[] {"snaptime", "aggregations", 86 "formattedStrings", "tuples", "output"}) 87 { 88 /* 89 * Need to prevent DefaultPersistenceDelegate from using 90 * overridden equals() method, resulting in a 91 * StackOverFlowError. Revert to PersistenceDelegate 92 * implementation. See 93 * http://forum.java.sun.com/thread.jspa?threadID= 94 * 477019&tstart=135 95 */ 96 protected boolean 97 mutatesTo(Object oldInstance, Object newInstance) 98 { 99 return (newInstance != null && oldInstance != null && 100 oldInstance.getClass() == newInstance.getClass()); 101 } 102 }; 103 BeanDescriptor d = info.getBeanDescriptor(); 104 d.setValue("persistenceDelegate", persistenceDelegate); 105 } catch (IntrospectionException e) { 106 System.out.println(e); 107 } 108 } 109 110 /** @serial */ 111 private final long snaptime; 112 /** @serial */ 113 private List <Aggregation> aggregations; 114 /** @serial */ 115 private Map <Tuple, String> formattedStrings; 116 /** @serial */ 117 private List <Tuple> tuples; 118 private transient StringBuilder outputBuffer; 119 private transient String output; 120 private transient boolean formatted; 121 122 /** 123 * Package level access, called by ProbeData. 124 */ PrintaRecord(long snaptimeNanos, boolean isFormatString)125 PrintaRecord(long snaptimeNanos, boolean isFormatString) 126 { 127 snaptime = snaptimeNanos; 128 aggregations = new ArrayList <Aggregation> (); 129 formattedStrings = new HashMap <Tuple, String> (); 130 tuples = new ArrayList <Tuple> (); 131 outputBuffer = new StringBuilder(); 132 formatted = isFormatString; 133 validate(); 134 } 135 136 /** 137 * Creates a record with the given snaptime, aggregations, and 138 * formatted output. 139 * 140 * @param snaptimeNanos nanosecond timestamp of the snapshot used 141 * to create this {@code printa()} record 142 * @param aggs aggregations passed to the {@code printa()} action 143 * that generated this record 144 * @param formattedOutput the formatted output, if any, associated 145 * with each {@code Tuple} occurring in the aggregations belonging 146 * to this record, one formatted string per {@code Tuple}, or an 147 * empty or {@code null} map if an incomplete {@code printa()} 148 * format string caused aggregation tuples to be omitted from this 149 * record 150 * @param orderedTuples list of aggregation tuples in the same order 151 * generated by the native DTrace library (determined by the various 152 * "aggsort" options such as {@link Option#aggsortkey}) 153 * @param formattedOutputString {@code printa()} formatted string 154 * output in the same order generated by the native DTrace library 155 * (determined by the various "aggsort" options such as 156 * {@link Option#aggsortkey}) 157 * @throws NullPointerException if the given collection of 158 * aggregations is {@code null}, or if the given ordered lists of 159 * tuples or formatted strings are {@code null} 160 * @throws IllegalArgumentException if the given snaptime is 161 * negative 162 */ 163 public PrintaRecord(long snaptimeNanos, Collection <Aggregation> aggs, Map <Tuple, String> formattedOutput, List <Tuple> orderedTuples, String formattedOutputString)164 PrintaRecord(long snaptimeNanos, Collection <Aggregation> aggs, 165 Map <Tuple, String> formattedOutput, 166 List <Tuple> orderedTuples, 167 String formattedOutputString) 168 { 169 snaptime = snaptimeNanos; 170 if (aggs != null) { 171 aggregations = new ArrayList <Aggregation> (aggs.size()); 172 aggregations.addAll(aggs); 173 } 174 if (formattedOutput != null) { 175 formattedStrings = new HashMap <Tuple, String> 176 (formattedOutput); 177 } 178 if (orderedTuples != null) { 179 tuples = new ArrayList <Tuple> (orderedTuples.size()); 180 tuples.addAll(orderedTuples); 181 } 182 output = formattedOutputString; 183 validate(); 184 } 185 186 private final void validate()187 validate() 188 { 189 if (snaptime < 0) { 190 throw new IllegalArgumentException("snaptime is negative"); 191 } 192 if (aggregations == null) { 193 throw new NullPointerException("aggregations list is null"); 194 } 195 Aggregation a; 196 for (int i = 0, len = aggregations.size(); i < len; ++i) { 197 a = aggregations.get(i); 198 if (a == null) { 199 throw new NullPointerException( 200 "null aggregation at index " + i); 201 } 202 } 203 if (tuples == null) { 204 throw new NullPointerException("ordered tuple list is null"); 205 } 206 if (output == null && outputBuffer == null) { 207 throw new NullPointerException("formatted output is null"); 208 } 209 } 210 211 /** 212 * Gets the nanosecond timestamp of the aggregate snapshot used to 213 * create this {@code printa()} record. 214 * 215 * @return nanosecond timestamp 216 */ 217 public long getSnaptime()218 getSnaptime() 219 { 220 return snaptime; 221 } 222 223 private Aggregation getAggregationImpl(String name)224 getAggregationImpl(String name) 225 { 226 if (name == null) { 227 return null; 228 } 229 for (Aggregation a : aggregations) { 230 if (name.equals(a.getName())) { 231 return a; 232 } 233 } 234 return null; 235 } 236 237 /** 238 * Gets the named aggregation. 239 * 240 * @return the named aggregation passed to {@code printa()}, or 241 * {@code null} if the named aggregation is not passed to {@code 242 * printa()}, or if it is omitted due to an incomplete {@code 243 * printa()} format string, or if it is empty (a future release of 244 * this API may represent an empty DTrace aggregation as a non-null 245 * {@code Aggregation} with no records; users of this API should not 246 * rely on a non-null return value to indicate a non-zero record 247 * count) 248 */ 249 public Aggregation getAggregation(String name)250 getAggregation(String name) 251 { 252 name = Aggregate.filterUnnamedAggregationName(name); 253 return getAggregationImpl(name); 254 } 255 256 /** 257 * Gets a list of the aggregations passed to the {@code printa()} 258 * action that generated this record. The returned list is a copy, 259 * and modifying it has no effect on this record. Supports XML 260 * persistence. 261 * 262 * @return non-null, possibly empty list of aggregations belonging 263 * to this record (empty aggregations are excluded) 264 */ 265 public List <Aggregation> getAggregations()266 getAggregations() 267 { 268 return new ArrayList <Aggregation> (aggregations); 269 } 270 271 /** 272 * Gets the formatted string, if any, associated with the given 273 * aggregation tuple. 274 * 275 * @param key aggregation tuple 276 * @return the formatted string associated with the given 277 * aggregation tuple, or {@code null} if the given tuple does not 278 * exist in the aggregations belonging to this record or if it 279 * is omitted from this record due to an incomplete {@code printa()} 280 * format string 281 * @see #getFormattedStrings() 282 * @see #getOutput() 283 */ 284 public String getFormattedString(Tuple key)285 getFormattedString(Tuple key) 286 { 287 if (formattedStrings == null) { 288 return null; 289 } 290 return formattedStrings.get(key); 291 } 292 293 /** 294 * Gets the formatted output, if any, associated with each {@code 295 * Tuple} occurring in the aggregations belonging to this record, 296 * one formatted string per {@code Tuple}. Gets an empty map if 297 * aggregation tuples are omitted from this record due to an 298 * incomplete {@code printa()} format string. The returned map is a 299 * copy and modifying it has no effect on this record. Supports XML 300 * persistence. 301 * 302 * @return a map of aggregation tuples and their associated 303 * formatted output strings, empty if aggregation tuples are omitted 304 * from this record due to an incomplete {@code printa(}) format 305 * string 306 * @see #getFormattedString(Tuple key) 307 * @see #getOutput() 308 */ 309 public Map <Tuple, String> getFormattedStrings()310 getFormattedStrings() 311 { 312 if (formattedStrings == null) { 313 return new HashMap <Tuple, String> (); 314 } 315 return new HashMap <Tuple, String> (formattedStrings); 316 } 317 318 /** 319 * Gets an ordered list of this record's aggregation tuples. The 320 * returned list is a copy, and modifying it has no effect on this 321 * record. Supports XML persistence. 322 * 323 * @return a non-null list of this record's aggregation tuples in 324 * the order they were generated by the native DTrace library, as 325 * determined by the {@link Option#aggsortkey}, {@link 326 * Option#aggsortrev}, {@link Option#aggsortpos}, and {@link 327 * Option#aggsortkeypos} options 328 */ 329 public List <Tuple> getTuples()330 getTuples() 331 { 332 return new ArrayList <Tuple> (tuples); 333 } 334 335 /** 336 * Gets this record's formatted output. Supports XML persistence. 337 * 338 * @return non-null formatted output in the order generated by the 339 * native DTrace library, as determined by the {@link 340 * Option#aggsortkey}, {@link Option#aggsortrev}, {@link 341 * Option#aggsortpos}, and {@link Option#aggsortkeypos} options 342 */ 343 public String getOutput()344 getOutput() 345 { 346 if (output == null) { 347 output = outputBuffer.toString(); 348 outputBuffer = null; 349 if ((output.length() == 0) && !formatted) { 350 output = "\n"; 351 } 352 } 353 return output; 354 } 355 356 /** 357 * Package level access, called by ProbeData. 358 * 359 * @throws NullPointerException if aggregationName is null 360 * @throws IllegalStateException if this PrintaRecord has an 361 * aggregation matching the given name and it already has an 362 * AggregationRecord with the same tuple key as the given record. 363 */ 364 void addRecord(String aggregationName, long aggid, AggregationRecord record)365 addRecord(String aggregationName, long aggid, AggregationRecord record) 366 { 367 if (formattedStrings == null) { 368 // printa() format string does not completely specify tuple 369 return; 370 } 371 372 aggregationName = Aggregate.filterUnnamedAggregationName( 373 aggregationName); 374 Aggregation aggregation = getAggregationImpl(aggregationName); 375 if (aggregation == null) { 376 aggregation = new Aggregation(aggregationName, aggid); 377 aggregations.add(aggregation); 378 } 379 try { 380 aggregation.addRecord(record); 381 } catch (IllegalArgumentException e) { 382 Map <Tuple, AggregationRecord> map = aggregation.asMap(); 383 AggregationRecord r = map.get(record.getTuple()); 384 // 385 // The printa() format string may specify the value of the 386 // aggregating action multiple times. While that changes 387 // the resulting formatted string associated with the tuple, 388 // we ignore the attempt to add the redundant record to the 389 // aggregation. 390 // 391 if (!r.equals(record)) { 392 throw e; 393 } 394 } 395 } 396 397 // 398 // Called from native code when the tuple is not completely 399 // specified in the printa() format string. 400 // 401 void invalidate()402 invalidate() 403 { 404 formattedStrings = null; 405 aggregations.clear(); 406 tuples.clear(); 407 } 408 409 void addFormattedString(Tuple tuple, String formattedString)410 addFormattedString(Tuple tuple, String formattedString) 411 { 412 if (tuple != null && formattedStrings != null) { 413 if (formattedStrings.containsKey(tuple)) { 414 throw new IllegalArgumentException("A formatted string " + 415 "for tuple " + tuple + " already exists."); 416 } else { 417 formattedStrings.put(tuple, formattedString); 418 tuples.add(tuple); 419 } 420 } 421 outputBuffer.append(formattedString); 422 } 423 424 /** 425 * Compares the specified object with this {@code PrintaRecord} for 426 * equality. Returns {@code true} if and only if the specified 427 * object is also a {@code PrintaRecord} and both records have the 428 * same aggregations and the same formatted strings in the same 429 * order (by aggregation tuple). 430 * 431 * @return {@code true} if and only if the specified object is also 432 * a {@code PrintaRecord} and both records have the same 433 * aggregations and the same formatted strings in the same order (by 434 * aggregation tuple) 435 */ 436 @Override 437 public boolean equals(Object o)438 equals(Object o) 439 { 440 if (o instanceof PrintaRecord) { 441 PrintaRecord r = (PrintaRecord)o; 442 return (aggregations.equals(r.aggregations) && 443 ((formattedStrings == null || formattedStrings.isEmpty()) 444 ? (r.formattedStrings == null || 445 r.formattedStrings.isEmpty()) 446 : formattedStrings.equals(r.formattedStrings)) && 447 tuples.equals(r.tuples)); 448 } 449 450 return false; 451 } 452 453 /** 454 * Overridden to ensure that equal instances have equal hash codes. 455 */ 456 @Override 457 public int hashCode()458 hashCode() 459 { 460 int hash = 17; 461 hash = (hash * 37) + aggregations.hashCode(); 462 hash = (hash * 37) + ((formattedStrings == null || 463 formattedStrings.isEmpty()) ? 0 : 464 formattedStrings.hashCode()); 465 hash = (hash * 37) + tuples.hashCode(); 466 return hash; 467 } 468 469 /** 470 * Compares the formatted {@link #getOutput() output} of this record 471 * with that of the given record. Note that ordering {@code printa} 472 * records by their output string values is incompatible with {@link 473 * #equals(Object o) equals()}, which also checks the underlying 474 * aggregation data for equality. 475 * 476 * @return a negative number, 0, or a positive number as this 477 * record's formatted output is lexicographically less than, equal 478 * to, or greater than the given record's formatted output 479 */ 480 public int compareTo(PrintaRecord r)481 compareTo(PrintaRecord r) 482 { 483 return getOutput().compareTo(r.getOutput()); 484 } 485 486 /** 487 * Serialize this {@code PrintaRecord} instance. 488 * 489 * @serialData Serialized fields are emitted, followed by the 490 * formatted output string. 491 */ 492 private void writeObject(ObjectOutputStream s)493 writeObject(ObjectOutputStream s) throws IOException 494 { 495 s.defaultWriteObject(); 496 if (output == null) { 497 s.writeObject(outputBuffer.toString()); 498 } else { 499 s.writeObject(output); 500 } 501 } 502 503 private void readObject(ObjectInputStream s)504 readObject(ObjectInputStream s) 505 throws IOException, ClassNotFoundException 506 { 507 s.defaultReadObject(); 508 output = (String)s.readObject(); 509 // make defensive copy 510 if (aggregations != null) { 511 List <Aggregation> copy = new ArrayList <Aggregation> 512 (aggregations.size()); 513 copy.addAll(aggregations); 514 aggregations = copy; 515 } 516 if (formattedStrings != null) { 517 formattedStrings = new HashMap <Tuple, String> (formattedStrings); 518 } 519 if (tuples != null) { 520 List <Tuple> copy = new ArrayList <Tuple> (tuples.size()); 521 copy.addAll(tuples); 522 tuples = copy; 523 } 524 // check constructor invariants only after defensive copy 525 try { 526 validate(); 527 } catch (Exception e) { 528 InvalidObjectException x = new InvalidObjectException( 529 e.getMessage()); 530 x.initCause(e); 531 throw x; 532 } 533 } 534 535 /** 536 * Gets a string representation of this instance useful for logging 537 * and not intended for display. The exact details of the 538 * representation are unspecified and subject to change, but the 539 * following format may be regarded as typical: 540 * <pre><code> 541 * class-name[property1 = value1, property2 = value2] 542 * </code></pre> 543 */ 544 public String toString()545 toString() 546 { 547 StringBuilder buf = new StringBuilder(); 548 buf.append(PrintaRecord.class.getName()); 549 buf.append("[snaptime = "); 550 buf.append(snaptime); 551 buf.append(", aggregations = "); 552 buf.append(aggregations); 553 buf.append(", formattedStrings = "); 554 buf.append(formattedStrings); 555 buf.append(", tuples = "); 556 buf.append(tuples); 557 buf.append(", output = "); 558 buf.append(getOutput()); 559 buf.append(']'); 560 return buf.toString(); 561 } 562 } 563