2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A *
2N/A * ident "%Z%%M% %I% %E% SMI"
2N/A */
2N/Apackage org.opensolaris.os.dtrace;
2N/A
2N/Aimport java.util.*;
2N/Aimport java.io.*;
2N/Aimport java.beans.*;
2N/A
2N/A/**
2N/A * A frequency distribution aggregated by the DTrace {@code quantize()}
2N/A * or {@code lquantize()} action. Each aggregated value falls into a
2N/A * range known as a bucket and counts toward the frequency of that
2N/A * bucket. Bucket ranges are consecutive, with the maximum of one
2N/A * bucket's range always one less than the minimum of the next bucket's
2N/A * range. By convention each bucket is identified by the minimum of its
2N/A * range.
2N/A *
2N/A * @author Tom Erickson
2N/A */
2N/Apublic abstract class Distribution implements AggregationValue,
2N/A Iterable <Distribution.Bucket>, Serializable
2N/A{
2N/A static final long serialVersionUID = 1186243118882654932L;
2N/A
2N/A /** @serial */
2N/A private List <Bucket> buckets;
2N/A private transient double total;
2N/A private transient boolean initialized;
2N/A
2N/A /**
2N/A * Package level access, called by subclasses LinearDistribution and
2N/A * LogDistribution, but not available outside the API.
2N/A *
2N/A * @param base the lower bound of this distribution, or zero if not
2N/A * applicable
2N/A * @param constant the constant term of the distribution function
2N/A * used to calculate the lower bound of any bucket given the lower
2N/A * bound of the previous bucket, for example the step in a linear
2N/A * distribution or the log base in a logarithmic distribution.
2N/A * @param frequencies for each bucket, the number of aggregated
2N/A * values falling into that bucket's range; each element must be a
2N/A * positive integer
2N/A * @throws NullPointerException if frequencies is null
2N/A * @throws IllegalArgumentException if any element of frequencies
2N/A * does not have the expected range as defined by checkBucketRange()
2N/A */
2N/A Distribution(long base, long constant, long[] frequencies)
2N/A {
2N/A total = 0;
2N/A long frequency;
2N/A for (int i = 0, len = frequencies.length; i < len; ++i) {
2N/A frequency = frequencies[i];
2N/A total += frequency;
2N/A }
2N/A
2N/A buckets = createBuckets(base, constant, frequencies);
2N/A initialized = true;
2N/A }
2N/A
2N/A /**
2N/A * Supports XML persistence of subclasses. Sub-class implementation
2N/A * must call initialize() after setting any state specific to that
2N/A * subclass for determining bucket ranges.
2N/A *
2N/A * @throws NullPointerException if frequencies is null
2N/A * @throws IllegalArgumentException if any element of frequencies
2N/A * does not have the expected range as defined by checkBucketRange()
2N/A */
2N/A Distribution(List <Bucket> frequencies)
2N/A {
2N/A // defensively copy frequencies list
2N/A int len = frequencies.size();
2N/A // don't need gratuitous capacity % added by constructor that
2N/A // takes a Collection argument; list will not be modified
2N/A buckets = new ArrayList <Bucket> (len);
2N/A buckets.addAll(frequencies);
2N/A }
2N/A
2N/A final void
2N/A initialize()
2N/A {
2N/A // Called by constructor and readObject() (deserialization).
2N/A // 1. Check class invariants, throw exception if deserialized
2N/A // state inconsistent with a Distribution that can result
2N/A // from the public constructor.
2N/A // 2. Compute total (transient property derived from buckets)
2N/A total = 0;
2N/A long frequency;
2N/A Bucket bucket;
2N/A int len = buckets.size();
2N/A for (int i = 0; i < len; ++i) {
2N/A bucket = buckets.get(i);
2N/A frequency = bucket.getFrequency();
2N/A // relies on package-private getBucketRange()
2N/A // implementation
2N/A checkBucketRange(i, len, bucket);
2N/A total += frequency;
2N/A }
2N/A initialized = true;
2N/A }
2N/A
2N/A // Must be called by public instance methods (since the AbstractList
2N/A // methods all depend on get() and size(), it is sufficient to call
2N/A // checkInit() only in those inherited methods).
2N/A private void
2N/A checkInit()
2N/A {
2N/A if (!initialized) {
2N/A throw new IllegalStateException("Uninitialized");
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets a two element array: the first elelemt is the range minimum
2N/A * (inclusive), the second element is the range maximum (inclusive).
2N/A * Implemented by subclasses LinearDistribution and LogDistribution
2N/A * to define bucket ranges for this distribution and not available
2N/A * outside the API. Used by the private general purpose constructor
2N/A * called from native code. Implementation must not use
2N/A * subclass-specific state, since subclass state has not yet been
2N/A * allocated.
2N/A *
2N/A * @see #Distribution(long base, long constant, long[] frequencies)
2N/A */
2N/A abstract long[]
2N/A getBucketRange(int i, int len, long base, long constant);
2N/A
2N/A /**
2N/A * Used by public constructors and deserialization only after
2N/A * state specific to the subclass is available to the method.
2N/A */
2N/A abstract long[]
2N/A getBucketRange(int i, int len);
2N/A
2N/A private List <Distribution.Bucket>
2N/A createBuckets(long base, long constant, long[] frequencies)
2N/A {
2N/A int len = frequencies.length;
2N/A Bucket bucket;
2N/A List <Bucket> buckets = new ArrayList <Bucket> (len);
2N/A long min; // current bucket
2N/A long max; // next bucket minus one
2N/A long[] range; // two element array: { min, max }
2N/A
2N/A for (int i = 0; i < len; ++i) {
2N/A range = getBucketRange(i, len, base, constant);
2N/A min = range[0];
2N/A max = range[1];
2N/A bucket = new Distribution.Bucket(min, max, frequencies[i]);
2N/A buckets.add(bucket);
2N/A }
2N/A
2N/A return buckets;
2N/A }
2N/A
2N/A /**
2N/A * Validates that bucket has the expected range for the given bucket
2N/A * index. Uses {@code base} and {@code constant} constructor args
2N/A * to check invariants specific to each subclass, since state
2N/A * derived from these args in a subclass is not yet available in the
2N/A * superclass constructor.
2N/A *
2N/A * @throws IllegalArgumentException if bucket does not have the
2N/A * expected range for the given bucket index {@code i}
2N/A */
2N/A private void
2N/A checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket,
2N/A long base, long constant)
2N/A {
2N/A long[] range = getBucketRange(i, bucketCount, base, constant);
2N/A checkBucketRange(i, bucket, range);
2N/A }
2N/A
2N/A private void
2N/A checkBucketRange(int i, int bucketCount, Distribution.Bucket bucket)
2N/A {
2N/A long[] range = getBucketRange(i, bucketCount);
2N/A checkBucketRange(i, bucket, range);
2N/A }
2N/A
2N/A private void
2N/A checkBucketRange(int i, Distribution.Bucket bucket, long[] range)
2N/A {
2N/A long min = range[0];
2N/A long max = range[1];
2N/A
2N/A if (bucket.getMin() != min) {
2N/A throw new IllegalArgumentException("bucket min " +
2N/A bucket.getMin() + " at index " + i + ", expected " + min);
2N/A }
2N/A if (bucket.getMax() != max) {
2N/A throw new IllegalArgumentException("bucket max " +
2N/A bucket.getMax() + " at index " + i + ", expected " + max);
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets a modifiable list of this distribution's buckets ordered by
2N/A * bucket range. Modifying the returned list has no effect on this
2N/A * distribution. Supports XML persistence.
2N/A *
2N/A * @return a modifiable list of this distribution's buckets ordered
2N/A * by bucket range
2N/A */
2N/A public List <Bucket>
2N/A getBuckets()
2N/A {
2N/A checkInit();
2N/A return new ArrayList <Bucket> (buckets);
2N/A }
2N/A
2N/A /**
2N/A * Gets a read-only {@code List} view of this distribution.
2N/A *
2N/A * @return a read-only {@code List} view of this distribution
2N/A */
2N/A public List <Bucket>
2N/A asList()
2N/A {
2N/A checkInit();
2N/A return Collections. <Bucket> unmodifiableList(buckets);
2N/A }
2N/A
2N/A /**
2N/A * Gets the number of buckets in this distribution.
2N/A *
2N/A * @return non-negative bucket count
2N/A */
2N/A public int
2N/A size()
2N/A {
2N/A checkInit();
2N/A return buckets.size();
2N/A }
2N/A
2N/A /**
2N/A * Gets the bucket at the given distribution index (starting at
2N/A * zero).
2N/A *
2N/A * @return non-null distribution bucket at the given zero-based
2N/A * index
2N/A */
2N/A public Bucket
2N/A get(int index)
2N/A {
2N/A checkInit();
2N/A return buckets.get(index);
2N/A }
2N/A
2N/A /**
2N/A * Gets an iterator over the buckets of this distribution.
2N/A *
2N/A * @return an iterator over the buckets of this distribution
2N/A */
2N/A public Iterator<Bucket>
2N/A iterator()
2N/A {
2N/A checkInit();
2N/A return buckets.iterator();
2N/A }
2N/A
2N/A /**
2N/A * Compares the specified object with this {@code Distribution}
2N/A * instance for equality. Defines equality as having the same
2N/A * buckets with the same values.
2N/A *
2N/A * @return {@code true} if and only if the specified object is of
2N/A * type {@code Distribution} and both instances have the same size
2N/A * and equal buckets at corresponding distribution indexes
2N/A */
2N/A public boolean
2N/A equals(Object o)
2N/A {
2N/A checkInit();
2N/A if (o instanceof Distribution) {
2N/A Distribution d = (Distribution)o;
2N/A return buckets.equals(d.buckets);
2N/A }
2N/A return false;
2N/A }
2N/A
2N/A /**
2N/A * Overridden to ensure that equals instances have equal hash codes.
2N/A */
2N/A public int
2N/A hashCode()
2N/A {
2N/A checkInit();
2N/A return buckets.hashCode();
2N/A }
2N/A
2N/A /**
2N/A * Gets the total frequency across all buckets.
2N/A *
2N/A * @return sum of the frequency of all buckets in this distribution
2N/A */
2N/A public double
2N/A getTotal()
2N/A {
2N/A checkInit();
2N/A return total;
2N/A }
2N/A
2N/A /**
2N/A * Gets the numeric value of this distribution used to compare
2N/A * distributions by overall magnitude, defined as the sum total of
2N/A * each bucket's frequency times the minimum of its range.
2N/A */
2N/A public abstract Number getValue();
2N/A
2N/A /**
2N/A * Called by native code
2N/A */
2N/A private void
2N/A normalizeBuckets(long normal)
2N/A {
2N/A for (Bucket b : buckets) {
2N/A b.frequency /= normal;
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * A range inclusive at both endpoints and a count of aggregated
2N/A * values that fall in that range. Buckets in a {@link
2N/A * Distribution} are consecutive, such that the max of one bucket is
2N/A * always one less than the min of the next bucket (or {@link
2N/A * Long#MAX_VALUE} if it is the last bucket in the {@code
2N/A * Distribution}).
2N/A * <p>
2N/A * Immutable. Supports persistence using {@link java.beans.XMLEncoder}.
2N/A */
2N/A public static final class Bucket implements Serializable {
2N/A static final long serialVersionUID = 4863264115375406295L;
2N/A
2N/A /** @serial */
2N/A private final long min;
2N/A /** @serial */
2N/A private final long max;
2N/A /** @serial */
2N/A private long frequency; // non-final so native code can normalize
2N/A
2N/A static {
2N/A try {
2N/A BeanInfo info = Introspector.getBeanInfo(Bucket.class);
2N/A PersistenceDelegate persistenceDelegate =
2N/A new DefaultPersistenceDelegate(
2N/A new String[] {"min", "max", "frequency"})
2N/A {
2N/A /*
2N/A * Need to prevent DefaultPersistenceDelegate from using
2N/A * overridden equals() method, resulting in a
2N/A * StackOverFlowError. Revert to PersistenceDelegate
2N/A * implementation. See
2N/A * http://forum.java.sun.com/thread.jspa?threadID=
2N/A * 477019&tstart=135
2N/A */
2N/A protected boolean
2N/A mutatesTo(Object oldInstance, Object newInstance)
2N/A {
2N/A return (newInstance != null && oldInstance != null &&
2N/A (oldInstance.getClass() ==
2N/A newInstance.getClass()));
2N/A }
2N/A };
2N/A BeanDescriptor d = info.getBeanDescriptor();
2N/A d.setValue("persistenceDelegate", persistenceDelegate);
2N/A } catch (IntrospectionException e) {
2N/A System.out.println(e);
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Creates a distribution bucket with the given range and
2N/A * frequency.
2N/A *
2N/A * @param rangeMinimumInclusive sets the lower bound (inclusive)
2N/A * returned by {@link #getMin()}
2N/A * @param rangeMaximumInclusive sets the upper bound (inclusive)
2N/A * returned by {@link #getMax()}
2N/A * @param valuesInRange sets the value frequency in this
2N/A * bucket's range returned by {@link #getFrequency()}
2N/A * @throws IllegalArgumentException if {@code
2N/A * rangeMaximumInclusive} is less than {@code
2N/A * rangeMinimumInclusive}
2N/A */
2N/A public
2N/A Bucket(long rangeMinimumInclusive, long rangeMaximumInclusive,
2N/A long valuesInRange)
2N/A {
2N/A if (rangeMaximumInclusive < rangeMinimumInclusive) {
2N/A throw new IllegalArgumentException("upper bound " +
2N/A rangeMaximumInclusive + " is less than lower bound " +
2N/A rangeMinimumInclusive);
2N/A }
2N/A
2N/A min = rangeMinimumInclusive;
2N/A max = rangeMaximumInclusive;
2N/A frequency = valuesInRange;
2N/A }
2N/A
2N/A /**
2N/A * Gets the lower bound of this bucket's range (inclusive).
2N/A */
2N/A public long
2N/A getMin()
2N/A {
2N/A return min;
2N/A }
2N/A
2N/A /**
2N/A * Gets the upper bound of this bucket's range (inclusive).
2N/A */
2N/A public long
2N/A getMax()
2N/A {
2N/A return max;
2N/A }
2N/A
2N/A /**
2N/A * Gets the number of values in a {@link Distribution} that fall
2N/A * into the range defined by this bucket.
2N/A */
2N/A public long
2N/A getFrequency()
2N/A {
2N/A return frequency;
2N/A }
2N/A
2N/A /**
2N/A * Compares the specified object with this distribution bucket
2N/A * for equality. Defines equality of two distribution buckets
2N/A * as having the same range and the same frequency.
2N/A *
2N/A * @return false if the specified object is not a {@code
2N/A * Distribution.Bucket}
2N/A */
2N/A @Override
2N/A public boolean
2N/A equals(Object o)
2N/A {
2N/A if (o instanceof Bucket) {
2N/A Bucket b = (Bucket)o;
2N/A return ((min == b.min) &&
2N/A (max == b.max) &&
2N/A (frequency == b.frequency));
2N/A }
2N/A return false;
2N/A }
2N/A
2N/A /**
2N/A * Overridden to ensure that equal buckets have equal hashcodes.
2N/A */
2N/A @Override
2N/A public int
2N/A hashCode()
2N/A {
2N/A int hash = 17;
2N/A hash = (37 * hash) + ((int)(min ^ (min >>> 32)));
2N/A hash = (37 * hash) + ((int)(max ^ (max >>> 32)));
2N/A hash = (37 * hash) + ((int)(frequency ^ (frequency >>> 32)));
2N/A return hash;
2N/A }
2N/A
2N/A private void
2N/A readObject(ObjectInputStream s)
2N/A throws IOException, ClassNotFoundException
2N/A {
2N/A s.defaultReadObject();
2N/A // check class invariants (as constructor does)
2N/A if (max < min) {
2N/A throw new InvalidObjectException("upper bound " +
2N/A max + " is less than lower bound " + min);
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets a string representation of this distribution bucket
2N/A * useful for logging and not intended for display. The exact
2N/A * details of the representation are unspecified and subject to
2N/A * change, but the following format may be regarded as typical:
2N/A * <pre><code>
2N/A * class-name[property1 = value1, property2 = value2]
2N/A * </code></pre>
2N/A */
2N/A public String
2N/A toString()
2N/A {
2N/A StringBuilder buf = new StringBuilder();
2N/A buf.append(Bucket.class.getName());
2N/A buf.append("[min = ");
2N/A buf.append(min);
2N/A buf.append(", max = ");
2N/A buf.append(max);
2N/A buf.append(", frequency = ");
2N/A buf.append(frequency);
2N/A buf.append(']');
2N/A return buf.toString();
2N/A }
2N/A }
2N/A
2N/A /**
2N/A * Gets a list of buckets of interest by excluding empty buckets at
2N/A * both ends of the distribution. Leaves one empty bucket on each
2N/A * end if possible to convey the distribution context more
2N/A * effectively in a display.
2N/A *
2N/A * @return an unmodifiable sublist that includes the range starting
2N/A * from the first bucket with a non-zero frequency and ending with
2N/A * the last bucket with a non-zero frequency, plus one empty bucket
2N/A * before and after that range if possible
2N/A */
2N/A public List <Bucket>
2N/A getDisplayRange()
2N/A {
2N/A checkInit();
2N/A int min = -1;
2N/A int max = -1;
2N/A int len = size();
2N/A Bucket b;
2N/A // Get first non-empty bucket
2N/A for (int i = 0; i < len; ++i) {
2N/A b = buckets.get(i);
2N/A if (b.getFrequency() > 0L) {
2N/A min = i;
2N/A break;
2N/A }
2N/A }
2N/A if (min < 0) {
2N/A return Collections. <Bucket> emptyList();
2N/A }
2N/A // Get last non-empty bucket
2N/A for (int i = (len - 1); i >= 0; --i) {
2N/A b = buckets.get(i);
2N/A if (b.getFrequency() > 0L) {
2N/A max = i;
2N/A break;
2N/A }
2N/A }
2N/A // If possible, pad non-empty range with one empty bucket at
2N/A // each end.
2N/A if (min > 0) {
2N/A --min;
2N/A }
2N/A if (max < (len - 1)) {
2N/A ++max;
2N/A }
2N/A
2N/A // subList inclusive at low index, exclusive at high index
2N/A return Collections. <Bucket>
2N/A unmodifiableList(buckets).subList(min, max + 1);
2N/A }
2N/A
2N/A private void
2N/A readObject(ObjectInputStream s)
2N/A throws IOException, ClassNotFoundException
2N/A {
2N/A s.defaultReadObject();
2N/A
2N/A // Defensively copy buckets _before_ validating. Subclass
2N/A // validates by calling initialize() after reading any state
2N/A // specific to that subclass needed for validation.
2N/A int len = buckets.size();
2N/A ArrayList <Bucket> copy = new ArrayList <Bucket> (len);
2N/A copy.addAll(buckets);
2N/A buckets = copy;
2N/A }
2N/A
2N/A /**
2N/A * Gets a string representation of this {@code Distribution} useful
2N/A * for logging and not intended for display. The exact details of
2N/A * the representation are unspecified and subject to change, but the
2N/A * following format may be regarded as typical:
2N/A * <pre><code>
2N/A * class-name[property1 = value1, property2 = value2]
2N/A * </code></pre>
2N/A */
2N/A public String
2N/A toString()
2N/A {
2N/A checkInit();
2N/A StringBuilder buf = new StringBuilder();
2N/A buf.append(Distribution.class.getName());
2N/A buf.append("[buckets = ");
2N/A List <Bucket> list = getDisplayRange();
2N/A if (list.isEmpty()) {
2N/A buf.append("<empty>");
2N/A } else {
2N/A buf.append(Arrays.toString(getDisplayRange().toArray()));
2N/A }
2N/A buf.append(", total = ");
2N/A buf.append(getTotal());
2N/A buf.append(']');
2N/A return buf.toString();
2N/A }
2N/A}