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