/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * ident "%Z%%M% %I% %E% SMI" */ package org.opensolaris.os.dtrace; import java.util.*; import java.io.*; import java.beans.*; /** * A frequency distribution aggregated by the DTrace {@code quantize()} * or {@code lquantize()} action. Each aggregated value falls into a * range known as a bucket and counts toward the frequency of that * bucket. Bucket ranges are consecutive, with the maximum of one * bucket's range always one less than the minimum of the next bucket's * range. By convention each bucket is identified by the minimum of its * range. * * @author Tom Erickson */ public abstract class Distribution implements AggregationValue, Iterable , Serializable { static final long serialVersionUID = 1186243118882654932L; /** @serial */ private List buckets; private transient double total; private transient boolean initialized; /** * Package level access, called by subclasses LinearDistribution and * LogDistribution, but not available outside the API. * * @param base the lower bound of this distribution, or zero if not * applicable * @param constant the constant term of the distribution function * used to calculate the lower bound of any bucket given the lower * bound of the previous bucket, for example the step in a linear * distribution or the log base in a logarithmic distribution. * @param frequencies for each bucket, the number of aggregated * values falling into that bucket's range; each element must be a * positive integer * @throws NullPointerException if frequencies is null * @throws IllegalArgumentException if any element of frequencies * does not have the expected range as defined by checkBucketRange() */ Distribution(long base, long constant, long[] frequencies) { total = 0; long frequency; for (int i = 0, len = frequencies.length; i < len; ++i) { frequency = frequencies[i]; total += frequency; } buckets = createBuckets(base, constant, frequencies); initialized = true; } /** * Supports XML persistence of subclasses. Sub-class implementation * must call initialize() after setting any state specific to that * subclass for determining bucket ranges. * * @throws NullPointerException if frequencies is null * @throws IllegalArgumentException if any element of frequencies * does not have the expected range as defined by checkBucketRange() */ Distribution(List frequencies) { // defensively copy frequencies list int len = frequencies.size(); // don't need gratuitous capacity % added by constructor that // takes a Collection argument; list will not be modified buckets = new ArrayList (len); buckets.addAll(frequencies); } final void initialize() { // Called by constructor and readObject() (deserialization). // 1. Check class invariants, throw exception if deserialized // state inconsistent with a Distribution that can result // from the public constructor. // 2. Compute total (transient property derived from buckets) total = 0; long frequency; Bucket bucket; int len = buckets.size(); for (int i = 0; i < len; ++i) { bucket = buckets.get(i); frequency = bucket.getFrequency(); // relies on package-private getBucketRange() // implementation checkBucketRange(i, len, bucket); total += frequency; } initialized = true; } // Must be called by public instance methods (since the AbstractList // methods all depend on get() and size(), it is sufficient to call // checkInit() only in those inherited methods). private void checkInit() { if (!initialized) { throw new IllegalStateException("Uninitialized"); } } /** * Gets a two element array: the first elelemt is the range minimum * (inclusive), the second element is the range maximum (inclusive). * Implemented by subclasses LinearDistribution and LogDistribution * to define bucket ranges for this distribution and not available * outside the API. Used by the private general purpose constructor * called from native code. Implementation must not use * subclass-specific state, since subclass state has not yet been * allocated. * * @see #Distribution(long base, long constant, long[] frequencies) */ abstract long[] getBucketRange(int i, int len, long base, long constant); /** * Used by public constructors and deserialization only after * state specific to the subclass is available to the method. */ abstract long[] getBucketRange(int i, int len); private List createBuckets(long base, long constant, long[] frequencies) { int len = frequencies.length; Bucket bucket; List buckets = new ArrayList (len); long min; // current bucket long max; // next bucket minus one long[] range; // two element array: { min, max } for (int i = 0; i < len; ++i) { range = getBucketRange(i, len, base, constant); min = range[0]; max = range[1]; bucket = new Distribution.Bucket(min, max, frequencies[i]); buckets.add(bucket); } return buckets; } /** * Validates that bucket has the expected range for the given bucket * index. Uses {@code base} and {@code constant} constructor args * to check invariants specific to each subclass, since state * derived from these args in a subclass is not yet available in the * superclass constructor. * * @throws IllegalArgumentException if bucket does not have the * expected range for the given bucket index {@code i} */ private void checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket, long base, long constant) { long[] range = getBucketRange(i, bucketCount, base, constant); checkBucketRange(i, bucket, range); } private void checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket) { long[] range = getBucketRange(i, bucketCount); checkBucketRange(i, bucket, range); } private void checkBucketRange(int i, Distribution.Bucket bucket, long[] range) { long min = range[0]; long max = range[1]; if (bucket.getMin() != min) { throw new IllegalArgumentException("bucket min " + bucket.getMin() + " at index " + i + ", expected " + min); } if (bucket.getMax() != max) { throw new IllegalArgumentException("bucket max " + bucket.getMax() + " at index " + i + ", expected " + max); } } /** * Gets a modifiable list of this distribution's buckets ordered by * bucket range. Modifying the returned list has no effect on this * distribution. Supports XML persistence. * * @return a modifiable list of this distribution's buckets ordered * by bucket range */ public List getBuckets() { checkInit(); return new ArrayList (buckets); } /** * Gets a read-only {@code List} view of this distribution. * * @return a read-only {@code List} view of this distribution */ public List asList() { checkInit(); return Collections. unmodifiableList(buckets); } /** * Gets the number of buckets in this distribution. * * @return non-negative bucket count */ public int size() { checkInit(); return buckets.size(); } /** * Gets the bucket at the given distribution index (starting at * zero). * * @return non-null distribution bucket at the given zero-based * index */ public Bucket get(int index) { checkInit(); return buckets.get(index); } /** * Gets an iterator over the buckets of this distribution. * * @return an iterator over the buckets of this distribution */ public Iterator iterator() { checkInit(); return buckets.iterator(); } /** * Compares the specified object with this {@code Distribution} * instance for equality. Defines equality as having the same * buckets with the same values. * * @return {@code true} if and only if the specified object is of * type {@code Distribution} and both instances have the same size * and equal buckets at corresponding distribution indexes */ public boolean equals(Object o) { checkInit(); if (o instanceof Distribution) { Distribution d = (Distribution)o; return buckets.equals(d.buckets); } return false; } /** * Overridden to ensure that equals instances have equal hash codes. */ public int hashCode() { checkInit(); return buckets.hashCode(); } /** * Gets the total frequency across all buckets. * * @return sum of the frequency of all buckets in this distribution */ public double getTotal() { checkInit(); return total; } /** * Gets the numeric value of this distribution used to compare * distributions by overall magnitude, defined as the sum total of * each bucket's frequency times the minimum of its range. */ public abstract Number getValue(); /** * Called by native code */ private void normalizeBuckets(long normal) { for (Bucket b : buckets) { b.frequency /= normal; } } /** * A range inclusive at both endpoints and a count of aggregated * values that fall in that range. Buckets in a {@link * Distribution} are consecutive, such that the max of one bucket is * always one less than the min of the next bucket (or {@link * Long#MAX_VALUE} if it is the last bucket in the {@code * Distribution}). *

* Immutable. Supports persistence using {@link java.beans.XMLEncoder}. */ public static final class Bucket implements Serializable { static final long serialVersionUID = 4863264115375406295L; /** @serial */ private final long min; /** @serial */ private final long max; /** @serial */ private long frequency; // non-final so native code can normalize static { try { BeanInfo info = Introspector.getBeanInfo(Bucket.class); PersistenceDelegate persistenceDelegate = new DefaultPersistenceDelegate( new String[] {"min", "max", "frequency"}) { /* * Need to prevent DefaultPersistenceDelegate from using * overridden equals() method, resulting in a * StackOverFlowError. Revert to PersistenceDelegate * implementation. See * http://forum.java.sun.com/thread.jspa?threadID= * 477019&tstart=135 */ protected boolean mutatesTo(Object oldInstance, Object newInstance) { return (newInstance != null && oldInstance != null && (oldInstance.getClass() == newInstance.getClass())); } }; BeanDescriptor d = info.getBeanDescriptor(); d.setValue("persistenceDelegate", persistenceDelegate); } catch (IntrospectionException e) { System.out.println(e); } } /** * Creates a distribution bucket with the given range and * frequency. * * @param rangeMinimumInclusive sets the lower bound (inclusive) * returned by {@link #getMin()} * @param rangeMaximumInclusive sets the upper bound (inclusive) * returned by {@link #getMax()} * @param valuesInRange sets the value frequency in this * bucket's range returned by {@link #getFrequency()} * @throws IllegalArgumentException if {@code * rangeMaximumInclusive} is less than {@code * rangeMinimumInclusive} */ public Bucket(long rangeMinimumInclusive, long rangeMaximumInclusive, long valuesInRange) { if (rangeMaximumInclusive < rangeMinimumInclusive) { throw new IllegalArgumentException("upper bound " + rangeMaximumInclusive + " is less than lower bound " + rangeMinimumInclusive); } min = rangeMinimumInclusive; max = rangeMaximumInclusive; frequency = valuesInRange; } /** * Gets the lower bound of this bucket's range (inclusive). */ public long getMin() { return min; } /** * Gets the upper bound of this bucket's range (inclusive). */ public long getMax() { return max; } /** * Gets the number of values in a {@link Distribution} that fall * into the range defined by this bucket. */ public long getFrequency() { return frequency; } /** * Compares the specified object with this distribution bucket * for equality. Defines equality of two distribution buckets * as having the same range and the same frequency. * * @return false if the specified object is not a {@code * Distribution.Bucket} */ @Override public boolean equals(Object o) { if (o instanceof Bucket) { Bucket b = (Bucket)o; return ((min == b.min) && (max == b.max) && (frequency == b.frequency)); } return false; } /** * Overridden to ensure that equal buckets have equal hashcodes. */ @Override public int hashCode() { int hash = 17; hash = (37 * hash) + ((int)(min ^ (min >>> 32))); hash = (37 * hash) + ((int)(max ^ (max >>> 32))); hash = (37 * hash) + ((int)(frequency ^ (frequency >>> 32))); return hash; } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // check class invariants (as constructor does) if (max < min) { throw new InvalidObjectException("upper bound " + max + " is less than lower bound " + min); } } /** * Gets a string representation of this distribution bucket * useful for logging and not intended for display. The exact * details of the representation are unspecified and subject to * change, but the following format may be regarded as typical: *


	 * class-name[property1 = value1, property2 = value2]
	 * 
*/ public String toString() { StringBuilder buf = new StringBuilder(); buf.append(Bucket.class.getName()); buf.append("[min = "); buf.append(min); buf.append(", max = "); buf.append(max); buf.append(", frequency = "); buf.append(frequency); buf.append(']'); return buf.toString(); } } /** * Gets a list of buckets of interest by excluding empty buckets at * both ends of the distribution. Leaves one empty bucket on each * end if possible to convey the distribution context more * effectively in a display. * * @return an unmodifiable sublist that includes the range starting * from the first bucket with a non-zero frequency and ending with * the last bucket with a non-zero frequency, plus one empty bucket * before and after that range if possible */ public List getDisplayRange() { checkInit(); int min = -1; int max = -1; int len = size(); Bucket b; // Get first non-empty bucket for (int i = 0; i < len; ++i) { b = buckets.get(i); if (b.getFrequency() > 0L) { min = i; break; } } if (min < 0) { return Collections. emptyList(); } // Get last non-empty bucket for (int i = (len - 1); i >= 0; --i) { b = buckets.get(i); if (b.getFrequency() > 0L) { max = i; break; } } // If possible, pad non-empty range with one empty bucket at // each end. if (min > 0) { --min; } if (max < (len - 1)) { ++max; } // subList inclusive at low index, exclusive at high index return Collections. unmodifiableList(buckets).subList(min, max + 1); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); // Defensively copy buckets _before_ validating. Subclass // validates by calling initialize() after reading any state // specific to that subclass needed for validation. int len = buckets.size(); ArrayList copy = new ArrayList (len); copy.addAll(buckets); buckets = copy; } /** * Gets a string representation of this {@code Distribution} useful * for logging and not intended for display. The exact details of * the representation are unspecified and subject to change, but the * following format may be regarded as typical: *

     * class-name[property1 = value1, property2 = value2]
     * 
*/ public String toString() { checkInit(); StringBuilder buf = new StringBuilder(); buf.append(Distribution.class.getName()); buf.append("[buckets = "); List list = getDisplayRange(); if (list.isEmpty()) { buf.append(""); } else { buf.append(Arrays.toString(getDisplayRange().toArray())); } buf.append(", total = "); buf.append(getTotal()); buf.append(']'); return buf.toString(); } }