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 2007 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * ident	"%Z%%M%	%I%	%E% SMI"
27 */
28package org.opensolaris.os.dtrace;
29
30import java.util.*;
31import java.io.*;
32import java.beans.*;
33
34/**
35 * A linear frequency distribution aggregated by the DTrace {@code
36 * lquantize()} action.  Aggregated values fall into consecutive ranges
37 * bounded by the step parameter of the {@code lquantize()} action.
38 * Each range, known as a bucket, begins at the {@code lquantize()}
39 * lower bound, or base, plus a multiple of the {@code lquantize()}
40 * step, unless it is the first bucket, which is the frequency of all
41 * aggregated values less than the base.  The last bucket counts all
42 * aggregated values greater than or equal to the {@code lquantize()}
43 * upper bound.  For example
44 * <pre>		{@code @ = lquantize(n, 0, 100, 10);}</pre>
45 * results in a distribution with a base of 0, an upper bound of 100,
46 * and a step of 10.  It has twelve buckets starting with {@code n < 0}
47 * and ending with {@code n >= 100}.  The buckets in between are {@code
48 * 0 .. 9}, {@code 10 .. 19}, etc.
49 * <p>
50 * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
51 *
52 * @see LogDistribution
53 * @see Aggregation
54 *
55 * @author Tom Erickson
56 */
57public final class LinearDistribution extends Distribution
58        implements Serializable, Comparable <LinearDistribution>
59{
60    static final long serialVersionUID = 7100080045858770132L;
61
62    /** @serial */
63    private long base;
64    /** @serial */
65    private long step;
66
67    static {
68	try {
69	    BeanInfo info = Introspector.getBeanInfo(LinearDistribution.class);
70	    PersistenceDelegate persistenceDelegate =
71		    new DefaultPersistenceDelegate(
72		    new String[] {"base", "step", "buckets" });
73	    BeanDescriptor d = info.getBeanDescriptor();
74	    d.setValue("persistenceDelegate", persistenceDelegate);
75	} catch (IntrospectionException e) {
76	    System.out.println(e);
77	}
78    }
79
80    /**
81     * Called by native C code
82     */
83    private
84    LinearDistribution(long lowerBound, long frequencyStep,
85	    long[] frequencies)
86    {
87	// initializes using lowerBound and frequencyStep
88	super(lowerBound, frequencyStep, frequencies);
89	base = lowerBound;
90	step = frequencyStep;
91    }
92
93    /**
94     * Creates a linear distribution with the given base, step, and
95     * frequencies.  Supports XML persistence.
96     *
97     * @param lowerBound also known as the <i>base</i>; the minimum of
98     * the second bucket in this distribution (the first bucket contains
99     * the frequency of everything lower than the base)
100     * @param bucketStep the distance between the lower bound of one
101     * bucket and the lower bound of the next consecutive bucket
102     * (excluding the first bucket)
103     * @param frequencies list of frequencies in each bucket range
104     * @throws NullPointerException if {@code frequencies} is {@code
105     * null}
106     * @throws IllegalArgumentException if the given step is less than
107     * one, or if any bucket does not have the expected range
108     * (consecutive steps)
109     */
110    public
111    LinearDistribution(long lowerBound, long bucketStep,
112	    List <Bucket> frequencies)
113    {
114	super(frequencies); // makes defensive copy
115	base = lowerBound;
116	step = bucketStep;
117	initialize(); // checks class invariants, calculates total
118	if (step < 1) {
119	    throw new IllegalArgumentException("step is less than one");
120	}
121    }
122
123    /**
124     * Gets a two element array: the first elelemt is the range minimum
125     * (inclusive), the second element is the range maximum (inclusive).
126     */
127    @Override
128    long[]
129    getBucketRange(int i, int len, long base, long step)
130    {
131	long min;
132	long max;
133	if (i == 0) {
134	    // first bucket is everything less than the base
135	    min = Long.MIN_VALUE;
136	} else {
137	    min = (base + ((i - 1) * step));
138	}
139
140	if (i == (len - 1)) {
141	    // last bucket is everything greater than or equal to
142	    // the upper bound
143	    max = Long.MAX_VALUE;
144	} else {
145	    max = ((base + (i * step)) - 1);
146	}
147
148	long[] range = new long[] {min, max};
149	return range;
150    }
151
152    @Override
153    long[]
154    getBucketRange(int i, int len)
155    {
156	return getBucketRange(i, len, base, step);
157    }
158
159    /**
160     * Gets the lower bound of this distribution.  In a linear
161     * distribution, the first bucket holds the frequency of all values
162     * less than the base, so the base is the minimum of the second
163     * bucket's range.
164     *
165     * @return the lower bound of this distribution
166     */
167    public long
168    getBase()
169    {
170	return base;
171    }
172
173    /**
174     * Gets the difference between the lower bounds of consecutive
175     * buckets after the first.
176     *
177     * @return the step between the lower bounds of consecutive buckets
178     * after the first
179     */
180    public long
181    getStep()
182    {
183	return step;
184    }
185
186    public Number
187    getValue()
188    {
189	double total = 0;
190	List <Distribution.Bucket> buckets = getBuckets();
191	int len = buckets.size();
192	Distribution.Bucket bucket;
193
194	if (len > 0) {
195	    bucket = buckets.get(0);
196	    total = (double)bucket.getFrequency() * (double)(getBase() - 1);
197	}
198	for (int i = 1; i < (len - 1); ++i) {
199	    bucket = buckets.get(i);
200	    total += (double)bucket.getFrequency() * (double)bucket.getMin();
201	}
202	if (len > 1) {
203	    bucket = buckets.get(len - 1);
204	    // There doesn't seem to be any reason to add one to the
205	    // minimum of the last bucket range, but that's how it's
206	    // implemented in libdtrace dt_aggregate.c.
207	    total += (double)bucket.getFrequency() *
208		    (double)(bucket.getMin() + 1);
209	}
210	return (new Double(total));
211    }
212
213    private long
214    getZeroBucketValue()
215    {
216	for (Distribution.Bucket b : this) {
217	    if (b.getMin() == 0) {
218		return b.getFrequency();
219	    }
220	}
221	return 0;
222    }
223
224    /**
225     * Compares the {@code double} values of {@link #getValue()} for
226     * overall magnitude, and if those are equal, compares the
227     * frequencies at zero if the distributions include a bucket whose
228     * range has a minimum of zero.
229     */
230    public int
231    compareTo(LinearDistribution d)
232    {
233	Number v1 = getValue();
234	Number v2 = d.getValue();
235	double d1 = v1.doubleValue();
236	double d2 = v2.doubleValue();
237	int cmp = (d1 < d2 ? -1 : (d1 > d2 ? 1 : 0));
238	if (cmp == 0) {
239	    long z1 = getZeroBucketValue();
240	    long z2 = d.getZeroBucketValue();
241	    cmp = (z1 < z2 ? -1 : (z1 > z2 ? 1 : 0));
242	}
243	return (cmp);
244    }
245
246    private void
247    readObject(ObjectInputStream s)
248            throws IOException, ClassNotFoundException
249    {
250	s.defaultReadObject();
251	try {
252	    initialize();
253	} catch (Exception e) {
254	    InvalidObjectException x = new InvalidObjectException(
255		    e.getMessage());
256	    x.initCause(e);
257	    throw x;
258	}
259	if (step < 1) {
260	    throw new InvalidObjectException("step is less than one");
261	}
262    }
263
264    /**
265     * Gets a string representation of this linear distribution useful
266     * for logging and not intended for display.  The exact details of
267     * the representation are unspecified and subject to change, but the
268     * following format may be regarded as typical:
269     * <pre><code>
270     * class-name[property1 = value1, property2 = value2]
271     * </code></pre>
272     */
273    public String
274    toString()
275    {
276	StringBuilder buf = new StringBuilder();
277	buf.append(LinearDistribution.class.toString());
278	buf.append("[base = ");
279	buf.append(getBase());
280	buf.append(", step = ");
281	buf.append(getStep());
282	buf.append(", buckets = ");
283	List <Bucket> list = getDisplayRange();
284	if (list.isEmpty()) {
285	    buf.append("<empty>");
286	} else {
287	    buf.append(Arrays.toString(getDisplayRange().toArray()));
288	}
289	buf.append(", total = ");
290	buf.append(getTotal());
291	buf.append(']');
292	return buf.toString();
293    }
294}
295