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 2006 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 static final long serialVersionUID = -4174277639915895694L; 80 81 static { 82 try { 83 BeanInfo info = Introspector.getBeanInfo(PrintaRecord.class); 84 PersistenceDelegate persistenceDelegate = 85 new DefaultPersistenceDelegate( 86 new String[] {"snaptime", "aggregations", 87 "formattedStrings", "tuples", "output"}); 88 BeanDescriptor d = info.getBeanDescriptor(); 89 d.setValue("persistenceDelegate", persistenceDelegate); 90 } catch (IntrospectionException e) { 91 System.out.println(e); 92 } 93 } 94 95 /** @serial */ 96 private final long snaptime; 97 /** @serial */ 98 private List <Aggregation> aggregations; 99 /** @serial */ 100 private Map <Tuple, String> formattedStrings; 101 /** @serial */ 102 private List <Tuple> tuples; 103 private transient StringBuffer outputBuffer; 104 private transient String output; 105 private transient boolean formatted; 106 107 /** 108 * Package level access, called by ProbeData. 109 */ 110 PrintaRecord(long snaptimeNanos, boolean isFormatString) 111 { 112 snaptime = snaptimeNanos; 113 aggregations = new ArrayList <Aggregation> (); 114 formattedStrings = new HashMap <Tuple, String> (); 115 tuples = new ArrayList <Tuple> (); 116 outputBuffer = new StringBuffer(); 117 formatted = isFormatString; 118 validate(); 119 } 120 121 /** 122 * Creates a record with the given snaptime, aggregations, and 123 * formatted output. 124 * 125 * @param snaptimeNanos nanosecond timestamp of the snapshot used 126 * to create this {@code printa()} record 127 * @param aggs aggregations passed to the {@code printa()} action 128 * that generated this record 129 * @param formattedOutput the formatted output, if any, associated 130 * with each {@code Tuple} occurring in the aggregations belonging 131 * to this record, one formatted string per {@code Tuple}, or an 132 * empty or {@code null} map if an incomplete {@code printa()} 133 * format string caused aggregation tuples to be omitted from this 134 * record 135 * @param orderedTuples list of aggregation tuples in the same order 136 * generated by the native DTrace library (determined by the various 137 * "aggsort" options such as {@link Option#aggsortkey}) 138 * @param formattedOutputString {@code printa()} formatted string 139 * output in the same order generated by the native DTrace library 140 * (determined by the various "aggsort" options such as 141 * {@link Option#aggsortkey}) 142 * @throws NullPointerException if the given collection of 143 * aggregations is {@code null}, or if the given ordered lists of 144 * tuples or formatted strings are {@code null} 145 * @throws IllegalArgumentException if the given snaptime is 146 * negative 147 */ 148 public 149 PrintaRecord(long snaptimeNanos, Collection <Aggregation> aggs, 150 Map <Tuple, String> formattedOutput, 151 List <Tuple> orderedTuples, 152 String formattedOutputString) 153 { 154 snaptime = snaptimeNanos; 155 if (aggs != null) { 156 aggregations = new ArrayList <Aggregation> (aggs.size()); 157 aggregations.addAll(aggs); 158 } 159 if (formattedOutput != null) { 160 formattedStrings = new HashMap <Tuple, String> 161 (formattedOutput); 162 } 163 if (orderedTuples != null) { 164 tuples = new ArrayList <Tuple> (orderedTuples.size()); 165 tuples.addAll(orderedTuples); 166 } 167 output = formattedOutputString; 168 validate(); 169 } 170 171 private void 172 validate() 173 { 174 if (snaptime < 0) { 175 throw new IllegalArgumentException("snaptime is negative"); 176 } 177 if (aggregations == null) { 178 throw new NullPointerException("aggregations list is null"); 179 } 180 Aggregation a; 181 for (int i = 0, len = aggregations.size(); i < len; ++i) { 182 a = aggregations.get(i); 183 if (a == null) { 184 throw new NullPointerException( 185 "null aggregation at index " + i); 186 } 187 } 188 if (tuples == null) { 189 throw new NullPointerException("ordered tuple list is null"); 190 } 191 if (output == null && outputBuffer == null) { 192 throw new NullPointerException("formatted output is null"); 193 } 194 } 195 196 /** 197 * Gets the nanosecond timestamp of the aggregate snapshot used to 198 * create this {@code printa()} record. 199 * 200 * @return nanosecond timestamp 201 */ 202 public long 203 getSnaptime() 204 { 205 return snaptime; 206 } 207 208 private Aggregation 209 getAggregationImpl(String name) 210 { 211 if (name == null) { 212 return null; 213 } 214 for (Aggregation a : aggregations) { 215 if (name.equals(a.getName())) { 216 return a; 217 } 218 } 219 return null; 220 } 221 222 /** 223 * Gets the named aggregation. 224 * 225 * @return the named aggregation passed to {@code printa()}, or 226 * {@code null} if the named aggregation is not passed to {@code 227 * printa()}, or if it is omitted due to an incomplete {@code 228 * printa()} format string, or if it is empty (a future release of 229 * this API may represent an empty DTrace aggregation as a non-null 230 * {@code Aggregation} with no records; users of this API should not 231 * rely on a non-null return value to indicate a non-zero record 232 * count) 233 */ 234 public Aggregation 235 getAggregation(String name) 236 { 237 name = Aggregate.filterUnnamedAggregationName(name); 238 return getAggregationImpl(name); 239 } 240 241 /** 242 * Gets a list of the aggregations passed to the {@code printa()} 243 * action that generated this record. The returned list is a copy, 244 * and modifying it has no effect on this record. Supports XML 245 * persistence. 246 * 247 * @return non-null, possibly empty list of aggregations belonging 248 * to this record (empty aggregations are excluded) 249 */ 250 public List <Aggregation> 251 getAggregations() 252 { 253 return new ArrayList <Aggregation> (aggregations); 254 } 255 256 /** 257 * Gets the formatted string, if any, associated with the given 258 * aggregation tuple. 259 * 260 * @param key aggregation tuple 261 * @return the formatted string associated with the given 262 * aggregation tuple, or {@code null} if the given tuple does not 263 * exist in the aggregations belonging to this record or if it 264 * is omitted from this record due to an incomplete {@code printa()} 265 * format string 266 * @see #getFormattedStrings() 267 * @see #getOutput() 268 */ 269 public String 270 getFormattedString(Tuple key) 271 { 272 if (formattedStrings == null) { 273 return null; 274 } 275 return formattedStrings.get(key); 276 } 277 278 /** 279 * Gets the formatted output, if any, associated with each {@code 280 * Tuple} occurring in the aggregations belonging to this record, 281 * one formatted string per {@code Tuple}. Gets an empty map if 282 * aggregation tuples are omitted from this record due to an 283 * incomplete {@code printa()} format string. The returned map is a 284 * copy and modifying it has no effect on this record. Supports XML 285 * persistence. 286 * 287 * @return a map of aggregation tuples and their associated 288 * formatted output strings, empty if aggregation tuples are omitted 289 * from this record due to an incomplete {@code printa(}) format 290 * string 291 * @see #getFormattedString(Tuple key) 292 * @see #getOutput() 293 */ 294 public Map <Tuple, String> 295 getFormattedStrings() 296 { 297 if (formattedStrings == null) { 298 return new HashMap <Tuple, String> (); 299 } 300 return new HashMap <Tuple, String> (formattedStrings); 301 } 302 303 /** 304 * Gets an ordered list of this record's aggregation tuples. The 305 * returned list is a copy, and modifying it has no effect on this 306 * record. Supports XML persistence. 307 * 308 * @return a non-null list of this record's aggregation tuples in 309 * the order they were generated by the native DTrace library, as 310 * determined by the {@link Option#aggsortkey}, {@link 311 * Option#aggsortrev}, {@link Option#aggsortpos}, and {@link 312 * Option#aggsortkeypos} options 313 */ 314 public List <Tuple> 315 getTuples() 316 { 317 return new ArrayList <Tuple> (tuples); 318 } 319 320 /** 321 * Gets this record's formatted output. Supports XML persistence. 322 * 323 * @return non-null formatted output in the order generated by the 324 * native DTrace library, as determined by the {@link 325 * Option#aggsortkey}, {@link Option#aggsortrev}, {@link 326 * Option#aggsortpos}, and {@link Option#aggsortkeypos} options 327 */ 328 public String 329 getOutput() 330 { 331 if (output == null) { 332 output = outputBuffer.toString(); 333 outputBuffer = null; 334 if ((output.length() == 0) && !formatted) { 335 output = "\n"; 336 } 337 } 338 return output; 339 } 340 341 /** 342 * Package level access, called by ProbeData. 343 * 344 * @throws NullPointerException if aggregationName is null 345 * @throws IllegalStateException if this PrintaRecord has an 346 * aggregation matching the given name and it already has an 347 * AggregationRecord with the same tuple key as the given record. 348 */ 349 void 350 addRecord(String aggregationName, long aggid, AggregationRecord record) 351 { 352 if (formattedStrings == null) { 353 // printa() format string does not completely specify tuple 354 return; 355 } 356 357 aggregationName = Aggregate.filterUnnamedAggregationName( 358 aggregationName); 359 Aggregation aggregation = getAggregationImpl(aggregationName); 360 if (aggregation == null) { 361 aggregation = new Aggregation(aggregationName, aggid); 362 aggregations.add(aggregation); 363 } 364 try { 365 aggregation.addRecord(record); 366 } catch (IllegalArgumentException e) { 367 Map <Tuple, AggregationRecord> map = aggregation.asMap(); 368 AggregationRecord r = map.get(record.getTuple()); 369 // 370 // The printa() format string may specify the value of the 371 // aggregating action multiple times. While that changes 372 // the resulting formatted string associated with the tuple, 373 // we ignore the attempt to add the redundant record to the 374 // aggregation. 375 // 376 if (!r.equals(record)) { 377 throw e; 378 } 379 } 380 } 381 382 // 383 // Called from native code when the tuple is not completely 384 // specified in the printa() format string. 385 // 386 void 387 invalidate() 388 { 389 formattedStrings = null; 390 aggregations.clear(); 391 tuples.clear(); 392 } 393 394 void 395 addFormattedString(Tuple tuple, String formattedString) 396 { 397 if (tuple != null && formattedStrings != null) { 398 if (formattedStrings.containsKey(tuple)) { 399 throw new IllegalArgumentException("A formatted string " + 400 "for tuple " + tuple + " already exists."); 401 } else { 402 formattedStrings.put(tuple, formattedString); 403 tuples.add(tuple); 404 } 405 } 406 outputBuffer.append(formattedString); 407 } 408 409 /** 410 * Serialize this {@code PrintaRecord} instance. 411 * 412 * @serialData Serialized fields are emitted, followed by the 413 * formatted output string. 414 */ 415 private void 416 writeObject(ObjectOutputStream s) throws IOException 417 { 418 s.defaultWriteObject(); 419 if (output == null) { 420 s.writeObject(outputBuffer.toString()); 421 } else { 422 s.writeObject(output); 423 } 424 } 425 426 private void 427 readObject(ObjectInputStream s) 428 throws IOException, ClassNotFoundException 429 { 430 s.defaultReadObject(); 431 output = (String)s.readObject(); 432 // make defensive copy 433 if (aggregations != null) { 434 List <Aggregation> copy = new ArrayList <Aggregation> 435 (aggregations.size()); 436 copy.addAll(aggregations); 437 aggregations = copy; 438 } 439 if (formattedStrings != null) { 440 formattedStrings = new HashMap <Tuple, String> (formattedStrings); 441 } 442 if (tuples != null) { 443 List <Tuple> copy = new ArrayList <Tuple> (tuples.size()); 444 copy.addAll(tuples); 445 tuples = copy; 446 } 447 // check constructor invariants only after defensize copy 448 try { 449 validate(); 450 } catch (Exception e) { 451 throw new InvalidObjectException(e.getMessage()); 452 } 453 } 454 455 /** 456 * Gets a string representation of this instance useful for logging 457 * and not intended for display. The exact details of the 458 * representation are unspecified and subject to change, but the 459 * following format may be regarded as typical: 460 * <pre><code> 461 * class-name[property1 = value1, property2 = value2] 462 * </code></pre> 463 */ 464 public String 465 toString() 466 { 467 StringBuffer buf = new StringBuffer(); 468 buf.append(PrintaRecord.class.getName()); 469 buf.append("[snaptime = "); 470 buf.append(snaptime); 471 buf.append(", aggregations = "); 472 buf.append(aggregations); 473 buf.append(", formattedStrings = "); 474 buf.append(formattedStrings); 475 buf.append(", tuples = "); 476 buf.append(tuples); 477 buf.append(", output = "); 478 buf.append(getOutput()); 479 buf.append(']'); 480 return buf.toString(); 481 } 482 } 483