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  */
28 package org.opensolaris.os.dtrace;
29 
30 import java.util.*;
31 import java.io.*;
32 import 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  */
57 public 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
LinearDistribution(long lowerBound, long frequencyStep, long[] frequencies)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
LinearDistribution(long lowerBound, long bucketStep, List <Bucket> frequencies)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[]
getBucketRange(int i, int len, long base, long step)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[]
getBucketRange(int i, int len)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
getBase()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
getStep()181     getStep()
182     {
183 	return step;
184     }
185 
186     public Number
getValue()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
getZeroBucketValue()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
compareTo(LinearDistribution d)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
readObject(ObjectInputStream s)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
toString()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