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.io.*; 32 import java.beans.*; 33 34 /** 35 * A frequency distribution aggregated by the DTrace {@code quantize()} 36 * or {@code lquantize()} action. Each aggregated value falls into a 37 * range known as a bucket and counts toward the frequency of that 38 * bucket. Bucket ranges are consecutive, with the maximum of one 39 * bucket's range always one less than the minimum of the next bucket's 40 * range. By convention each bucket is identified by the minimum of its 41 * range. 42 * 43 * @author Tom Erickson 44 */ 45 public abstract class Distribution implements AggregationValue, 46 Iterable <Distribution.Bucket>, Serializable 47 { 48 static final long serialVersionUID = 1186243118882654932L; 49 50 /** @serial */ 51 private List <Bucket> buckets; 52 private transient double total; 53 private transient boolean initialized; 54 55 /** 56 * Package level access, called by subclasses LinearDistribution and 57 * LogDistribution, but not available outside the API. 58 * 59 * @param base the lower bound of this distribution, or zero if not 60 * applicable 61 * @param constant the constant term of the distribution function 62 * used to calculate the lower bound of any bucket given the lower 63 * bound of the previous bucket, for example the step in a linear 64 * distribution or the log base in a logarithmic distribution. 65 * @param frequencies for each bucket, the number of aggregated 66 * values falling into that bucket's range; each element must be a 67 * positive integer 68 * @throws NullPointerException if frequencies is null 69 * @throws IllegalArgumentException if any element of frequencies 70 * does not have the expected range as defined by checkBucketRange() 71 */ 72 Distribution(long base, long constant, long[] frequencies) 73 { 74 total = 0; 75 long frequency; 76 for (int i = 0, len = frequencies.length; i < len; ++i) { 77 frequency = frequencies[i]; 78 total += frequency; 79 } 80 81 buckets = createBuckets(base, constant, frequencies); 82 initialized = true; 83 } 84 85 /** 86 * Supports XML persistence of subclasses. Sub-class implementation 87 * must call initialize() after setting any state specific to that 88 * subclass for determining bucket ranges. 89 * 90 * @throws NullPointerException if frequencies is null 91 * @throws IllegalArgumentException if any element of frequencies 92 * does not have the expected range as defined by checkBucketRange() 93 */ 94 Distribution(List <Bucket> frequencies) 95 { 96 // defensively copy frequencies list 97 int len = frequencies.size(); 98 // don't need gratuitous capacity % added by constructor that 99 // takes a Collection argument; list will not be modified 100 buckets = new ArrayList <Bucket> (len); 101 buckets.addAll(frequencies); 102 } 103 104 final void 105 initialize() 106 { 107 // Called by constructor and readObject() (deserialization). 108 // 1. Check class invariants, throw exception if deserialized 109 // state inconsistent with a Distribution that can result 110 // from the public constructor. 111 // 2. Compute total (transient property derived from buckets) 112 total = 0; 113 long frequency; 114 Bucket bucket; 115 int len = buckets.size(); 116 for (int i = 0; i < len; ++i) { 117 bucket = buckets.get(i); 118 frequency = bucket.getFrequency(); 119 // relies on package-private getBucketRange() 120 // implementation 121 checkBucketRange(i, len, bucket); 122 total += frequency; 123 } 124 initialized = true; 125 } 126 127 // Must be called by public instance methods (since the AbstractList 128 // methods all depend on get() and size(), it is sufficient to call 129 // checkInit() only in those inherited methods). 130 private void 131 checkInit() 132 { 133 if (!initialized) { 134 throw new IllegalStateException("Uninitialized"); 135 } 136 } 137 138 /** 139 * Gets a two element array: the first elelemt is the range minimum 140 * (inclusive), the second element is the range maximum (inclusive). 141 * Implemented by subclasses LinearDistribution and LogDistribution 142 * to define bucket ranges for this distribution and not available 143 * outside the API. Used by the private general purpose constructor 144 * called from native code. Implementation must not use 145 * subclass-specific state, since subclass state has not yet been 146 * allocated. 147 * 148 * @see #Distribution(long base, long constant, long[] frequencies) 149 */ 150 abstract long[] 151 getBucketRange(int i, int len, long base, long constant); 152 153 /** 154 * Used by public constructors and deserialization only after 155 * state specific to the subclass is available to the method. 156 */ 157 abstract long[] 158 getBucketRange(int i, int len); 159 160 private List <Distribution.Bucket> 161 createBuckets(long base, long constant, long[] frequencies) 162 { 163 int len = frequencies.length; 164 Bucket bucket; 165 List <Bucket> buckets = new ArrayList <Bucket> (len); 166 long min; // current bucket 167 long max; // next bucket minus one 168 long[] range; // two element array: { min, max } 169 170 for (int i = 0; i < len; ++i) { 171 range = getBucketRange(i, len, base, constant); 172 min = range[0]; 173 max = range[1]; 174 bucket = new Distribution.Bucket(min, max, frequencies[i]); 175 buckets.add(bucket); 176 } 177 178 return buckets; 179 } 180 181 /** 182 * Validates that bucket has the expected range for the given bucket 183 * index. Uses {@code base} and {@code constant} constructor args 184 * to check invariants specific to each subclass, since state 185 * derived from these args in a subclass is not yet available in the 186 * superclass constructor. 187 * 188 * @throws IllegalArgumentException if bucket does not have the 189 * expected range for the given bucket index {@code i} 190 */ 191 private void 192 checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket, 193 long base, long constant) 194 { 195 long[] range = getBucketRange(i, bucketCount, base, constant); 196 checkBucketRange(i, bucket, range); 197 } 198 199 private void 200 checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket) 201 { 202 long[] range = getBucketRange(i, bucketCount); 203 checkBucketRange(i, bucket, range); 204 } 205 206 private void 207 checkBucketRange(int i, Distribution.Bucket bucket, long[] range) 208 { 209 long min = range[0]; 210 long max = range[1]; 211 212 if (bucket.getMin() != min) { 213 throw new IllegalArgumentException("bucket min " + 214 bucket.getMin() + " at index " + i + ", expected " + min); 215 } 216 if (bucket.getMax() != max) { 217 throw new IllegalArgumentException("bucket max " + 218 bucket.getMax() + " at index " + i + ", expected " + max); 219 } 220 } 221 222 /** 223 * Gets a modifiable list of this distribution's buckets ordered by 224 * bucket range. Modifying the returned list has no effect on this 225 * distribution. Supports XML persistence. 226 * 227 * @return a modifiable list of this distribution's buckets ordered 228 * by bucket range 229 */ 230 public List <Bucket> 231 getBuckets() 232 { 233 checkInit(); 234 return new ArrayList <Bucket> (buckets); 235 } 236 237 /** 238 * Gets a read-only {@code List} view of this distribution. 239 * 240 * @return a read-only {@code List} view of this distribution 241 */ 242 public List <Bucket> 243 asList() 244 { 245 checkInit(); 246 return Collections. <Bucket> unmodifiableList(buckets); 247 } 248 249 /** 250 * Gets the number of buckets in this distribution. 251 * 252 * @return non-negative bucket count 253 */ 254 public int 255 size() 256 { 257 checkInit(); 258 return buckets.size(); 259 } 260 261 /** 262 * Gets the bucket at the given distribution index (starting at 263 * zero). 264 * 265 * @return non-null distribution bucket at the given zero-based 266 * index 267 */ 268 public Bucket 269 get(int index) 270 { 271 checkInit(); 272 return buckets.get(index); 273 } 274 275 /** 276 * Gets an iterator over the buckets of this distribution. 277 * 278 * @return an iterator over the buckets of this distribution 279 */ 280 public Iterator<Bucket> 281 iterator() 282 { 283 checkInit(); 284 return buckets.iterator(); 285 } 286 287 /** 288 * Compares the specified object with this {@code Distribution} 289 * instance for equality. Defines equality as having the same 290 * buckets with the same values. 291 * 292 * @return {@code true} if and only if the specified object is of 293 * type {@code Distribution} and both instances have the same size 294 * and equal buckets at corresponding distribution indexes 295 */ 296 public boolean 297 equals(Object o) 298 { 299 checkInit(); 300 if (o instanceof Distribution) { 301 Distribution d = (Distribution)o; 302 return buckets.equals(d.buckets); 303 } 304 return false; 305 } 306 307 /** 308 * Overridden to ensure that equals instances have equal hash codes. 309 */ 310 public int 311 hashCode() 312 { 313 checkInit(); 314 return buckets.hashCode(); 315 } 316 317 /** 318 * Gets the total frequency across all buckets. 319 * 320 * @return sum of the frequency of all buckets in this distribution 321 */ 322 public double 323 getTotal() 324 { 325 checkInit(); 326 return total; 327 } 328 329 /** 330 * Gets the numeric value of this distribution used to compare 331 * distributions by overall magnitude, defined as the sum total of 332 * each bucket's frequency times the minimum of its range. 333 */ 334 public abstract Number getValue(); 335 336 /** 337 * Called by native code 338 */ 339 private void 340 normalizeBuckets(long normal) 341 { 342 for (Bucket b : buckets) { 343 b.frequency /= normal; 344 } 345 } 346 347 /** 348 * A range inclusive at both endpoints and a count of aggregated 349 * values that fall in that range. Buckets in a {@link 350 * Distribution} are consecutive, such that the max of one bucket is 351 * always one less than the min of the next bucket (or {@link 352 * Long#MAX_VALUE} if it is the last bucket in the {@code 353 * Distribution}). 354 * <p> 355 * Immutable. Supports persistence using {@link java.beans.XMLEncoder}. 356 */ 357 public static final class Bucket implements Serializable { 358 static final long serialVersionUID = 4863264115375406295L; 359 360 /** @serial */ 361 private final long min; 362 /** @serial */ 363 private final long max; 364 /** @serial */ 365 private long frequency; // non-final so native code can normalize 366 367 static { 368 try { 369 BeanInfo info = Introspector.getBeanInfo(Bucket.class); 370 PersistenceDelegate persistenceDelegate = 371 new DefaultPersistenceDelegate( 372 new String[] {"min", "max", "frequency"}) 373 { 374 /* 375 * Need to prevent DefaultPersistenceDelegate from using 376 * overridden equals() method, resulting in a 377 * StackOverFlowError. Revert to PersistenceDelegate 378 * implementation. See 379 * http://forum.java.sun.com/thread.jspa?threadID= 380 * 477019&tstart=135 381 */ 382 protected boolean 383 mutatesTo(Object oldInstance, Object newInstance) 384 { 385 return (newInstance != null && oldInstance != null && 386 (oldInstance.getClass() == 387 newInstance.getClass())); 388 } 389 }; 390 BeanDescriptor d = info.getBeanDescriptor(); 391 d.setValue("persistenceDelegate", persistenceDelegate); 392 } catch (IntrospectionException e) { 393 System.out.println(e); 394 } 395 } 396 397 /** 398 * Creates a distribution bucket with the given range and 399 * frequency. 400 * 401 * @param rangeMinimumInclusive sets the lower bound (inclusive) 402 * returned by {@link #getMin()} 403 * @param rangeMaximumInclusive sets the upper bound (inclusive) 404 * returned by {@link #getMax()} 405 * @param valuesInRange sets the value frequency in this 406 * bucket's range returned by {@link #getFrequency()} 407 * @throws IllegalArgumentException if {@code 408 * rangeMaximumInclusive} is less than {@code 409 * rangeMinimumInclusive} 410 */ 411 public 412 Bucket(long rangeMinimumInclusive, long rangeMaximumInclusive, 413 long valuesInRange) 414 { 415 if (rangeMaximumInclusive < rangeMinimumInclusive) { 416 throw new IllegalArgumentException("upper bound " + 417 rangeMaximumInclusive + " is less than lower bound " + 418 rangeMinimumInclusive); 419 } 420 421 min = rangeMinimumInclusive; 422 max = rangeMaximumInclusive; 423 frequency = valuesInRange; 424 } 425 426 /** 427 * Gets the lower bound of this bucket's range (inclusive). 428 */ 429 public long 430 getMin() 431 { 432 return min; 433 } 434 435 /** 436 * Gets the upper bound of this bucket's range (inclusive). 437 */ 438 public long 439 getMax() 440 { 441 return max; 442 } 443 444 /** 445 * Gets the number of values in a {@link Distribution} that fall 446 * into the range defined by this bucket. 447 */ 448 public long 449 getFrequency() 450 { 451 return frequency; 452 } 453 454 /** 455 * Compares the specified object with this distribution bucket 456 * for equality. Defines equality of two distribution buckets 457 * as having the same range and the same frequency. 458 * 459 * @return false if the specified object is not a {@code 460 * Distribution.Bucket} 461 */ 462 @Override 463 public boolean 464 equals(Object o) 465 { 466 if (o instanceof Bucket) { 467 Bucket b = (Bucket)o; 468 return ((min == b.min) && 469 (max == b.max) && 470 (frequency == b.frequency)); 471 } 472 return false; 473 } 474 475 /** 476 * Overridden to ensure that equal buckets have equal hashcodes. 477 */ 478 @Override 479 public int 480 hashCode() 481 { 482 int hash = 17; 483 hash = (37 * hash) + ((int)(min ^ (min >>> 32))); 484 hash = (37 * hash) + ((int)(max ^ (max >>> 32))); 485 hash = (37 * hash) + ((int)(frequency ^ (frequency >>> 32))); 486 return hash; 487 } 488 489 private void 490 readObject(ObjectInputStream s) 491 throws IOException, ClassNotFoundException 492 { 493 s.defaultReadObject(); 494 // check class invariants (as constructor does) 495 if (max < min) { 496 throw new InvalidObjectException("upper bound " + 497 max + " is less than lower bound " + min); 498 } 499 } 500 501 /** 502 * Gets a string representation of this distribution bucket 503 * useful for logging and not intended for display. The exact 504 * details of the representation are unspecified and subject to 505 * change, but the following format may be regarded as typical: 506 * <pre><code> 507 * class-name[property1 = value1, property2 = value2] 508 * </code></pre> 509 */ 510 public String 511 toString() 512 { 513 StringBuilder buf = new StringBuilder(); 514 buf.append(Bucket.class.getName()); 515 buf.append("[min = "); 516 buf.append(min); 517 buf.append(", max = "); 518 buf.append(max); 519 buf.append(", frequency = "); 520 buf.append(frequency); 521 buf.append(']'); 522 return buf.toString(); 523 } 524 } 525 526 /** 527 * Gets a list of buckets of interest by excluding empty buckets at 528 * both ends of the distribution. Leaves one empty bucket on each 529 * end if possible to convey the distribution context more 530 * effectively in a display. 531 * 532 * @return an unmodifiable sublist that includes the range starting 533 * from the first bucket with a non-zero frequency and ending with 534 * the last bucket with a non-zero frequency, plus one empty bucket 535 * before and after that range if possible 536 */ 537 public List <Bucket> 538 getDisplayRange() 539 { 540 checkInit(); 541 int min = -1; 542 int max = -1; 543 int len = size(); 544 Bucket b; 545 // Get first non-empty bucket 546 for (int i = 0; i < len; ++i) { 547 b = buckets.get(i); 548 if (b.getFrequency() > 0L) { 549 min = i; 550 break; 551 } 552 } 553 if (min < 0) { 554 return Collections. <Bucket> emptyList(); 555 } 556 // Get last non-empty bucket 557 for (int i = (len - 1); i >= 0; --i) { 558 b = buckets.get(i); 559 if (b.getFrequency() > 0L) { 560 max = i; 561 break; 562 } 563 } 564 // If possible, pad non-empty range with one empty bucket at 565 // each end. 566 if (min > 0) { 567 --min; 568 } 569 if (max < (len - 1)) { 570 ++max; 571 } 572 573 // subList inclusive at low index, exclusive at high index 574 return Collections. <Bucket> 575 unmodifiableList(buckets).subList(min, max + 1); 576 } 577 578 private void 579 readObject(ObjectInputStream s) 580 throws IOException, ClassNotFoundException 581 { 582 s.defaultReadObject(); 583 584 // Defensively copy buckets _before_ validating. Subclass 585 // validates by calling initialize() after reading any state 586 // specific to that subclass needed for validation. 587 int len = buckets.size(); 588 ArrayList <Bucket> copy = new ArrayList <Bucket> (len); 589 copy.addAll(buckets); 590 buckets = copy; 591 } 592 593 /** 594 * Gets a string representation of this {@code Distribution} useful 595 * for logging and not intended for display. The exact details of 596 * the representation are unspecified and subject to change, but the 597 * following format may be regarded as typical: 598 * <pre><code> 599 * class-name[property1 = value1, property2 = value2] 600 * </code></pre> 601 */ 602 public String 603 toString() 604 { 605 checkInit(); 606 StringBuilder buf = new StringBuilder(); 607 buf.append(Distribution.class.getName()); 608 buf.append("[buckets = "); 609 List <Bucket> list = getDisplayRange(); 610 if (list.isEmpty()) { 611 buf.append("<empty>"); 612 } else { 613 buf.append(Arrays.toString(getDisplayRange().toArray())); 614 } 615 buf.append(", total = "); 616 buf.append(getTotal()); 617 buf.append(']'); 618 return buf.toString(); 619 } 620 } 621