root/usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/StddevValue.java
/*
 * 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.io.*;
import java.beans.*;
import java.math.BigInteger;

/**
 * A {@code long} value aggregated by the DTrace {@code stddev()} action.
 * <p>
 * Immutable.  Supports persistence using {@link java.beans.XMLEncoder}.
 *
 * @see Aggregation
 * @author Tom Erickson
 */
public final class StddevValue extends AbstractAggregationValue {
    static final long serialVersionUID = 6409878160513885375L;

    /** @serial */
    private final long total;
    /** @serial */
    private final long count;
    /** @serial */
    private final BigInteger totalSquares;

    static {
        try {
            BeanInfo info = Introspector.getBeanInfo(StddevValue.class);
            PersistenceDelegate persistenceDelegate =
                    new DefaultPersistenceDelegate(
                    new String[] {"value", "total", "count", "totalSquares"})
            {
                @Override
                protected Expression
                instantiate(Object oldInstance, Encoder out)
                {
                    StddevValue stddev = (StddevValue)oldInstance;
                    return new Expression(oldInstance, oldInstance.getClass(),
                            "new", new Object[] {
                            stddev.getValue().longValue(),
                            stddev.getTotal(), stddev.getCount(),
                            stddev.getTotalSquares().toString() });
                }
            };
            BeanDescriptor d = info.getBeanDescriptor();
            d.setValue("persistenceDelegate", persistenceDelegate);
        } catch (IntrospectionException e) {
            System.out.println(e);
        }
    }

    // ported from dt_sqrt_128 in lib/libdtrace/common/dt_consume.c
    private static long
    squareRoot128(BigInteger n)
    {
        long result = 0;
        BigInteger diff = BigInteger.valueOf(0);
        BigInteger nextTry = BigInteger.valueOf(0);
        int bitPairs = (n.bitLength() / 2);
        int bitPos = (bitPairs * 2) + 1;
        int nextTwoBits;

        for (int i = 0; i <= bitPairs; i++) {
            // Bring down the next pair of bits.
            nextTwoBits = n.testBit(bitPos)
                    ? (n.testBit(bitPos - 1) ? 0x3 : 0x2)
                    : (n.testBit(bitPos - 1) ? 0x1 : 0x0);

            diff = diff.shiftLeft(2);
            diff = diff.add(BigInteger.valueOf(nextTwoBits));

            // nextTry = R << 2 + 1
            nextTry = BigInteger.valueOf(result);
            nextTry = nextTry.shiftLeft(2);
            nextTry = nextTry.setBit(0);

            result <<= 1;
            if (nextTry.compareTo(diff) <= 0) {
                diff = diff.subtract(nextTry);
                result++;
            }

            bitPos -= 2;
        }

        return (result);
    }

    // ported from dt_stddev in lib/libdtrace/common/dt_consume.c
    private static long
    standardDeviation(long stddevCount, long stddevTotal,
            BigInteger stddevTotalSquares)
    {
        BigInteger averageOfSquares = stddevTotalSquares.divide(
                BigInteger.valueOf(stddevCount));
        long avg = (stddevTotal / stddevCount);
        if (avg < 0) {
            avg = -avg;
        }
        BigInteger squareOfAverage = BigInteger.valueOf(avg);
        squareOfAverage = squareOfAverage.pow(2);
        BigInteger stddev = averageOfSquares.subtract(squareOfAverage);
        return squareRoot128(stddev);
    }

    /*
     * Called by native code.
     */
    private
    StddevValue(long stddevCount, long stddevTotal,
            BigInteger stddevTotalSquares)
    {
        super(stddevCount == 0 ? 0 : standardDeviation(stddevCount,
                stddevTotal, stddevTotalSquares));
        total = stddevTotal;
        count = stddevCount;
        totalSquares = stddevTotalSquares;
        if (totalSquares == null) {
            throw new NullPointerException("totalSquares is null");
        }
        if (count < 0) {
            throw new IllegalArgumentException("count is negative");
        }
    }

    /**
     * Creates a value aggregated by the DTrace {@code stddev()} action.
     * Supports XML persistence.
     *
     * @param v standard deviation
     * @param stddevTotal sum total of all values included in the standard
     * deviation
     * @param stddevCount number of values included in the standard
     * deviation
     * @param stddevTotalSquaresString decimal string representation of
     * the 128-bit sum total of the squares of all values included in
     * the standard deviation
     * @throws IllegalArgumentException if the given count is negative
     * or if the given standard deviation is not the value expected for
     * the given total, total of squares, and count
     * @throws NumberFormatException if the given total squares is not a
     * valid integer representation
     */
    public
    StddevValue(long v, long stddevTotal, long stddevCount,
            String stddevTotalSquaresString)
    {
        super(v);
        total = stddevTotal;
        count = stddevCount;
        totalSquares = new BigInteger(stddevTotalSquaresString);
        validate();
    }

    private final void
    validate()
    {
        if (count < 0) {
            throw new IllegalArgumentException("count is negative");
        }
        long stddev = super.getValue().longValue();
        if (count == 0) {
            if (stddev != 0) {
                throw new IllegalArgumentException(
                        "count of values is zero, stddev is non-zero (" +
                        stddev + ")");
            }
        } else {
            if (stddev != standardDeviation(count, total, totalSquares)) {
                throw new IllegalArgumentException(
                        getValue().toString() + " is not the expected " +
                        "standard deviation of total " + total + ", count " +
                        count + ", and total squares " + totalSquares);
            }
        }
    }

    // Needed to support XML persistence since XMLDecoder cannot find
    // the public method of the non-public superclass.

    /**
     * Gets the standard deviation of the aggregated values.
     *
     * @return standard deviation of the aggregated values
     */
    public Long
    getValue()
    {
        return (Long)super.getValue();
    }

    /**
     * Gets the sum total of the aggregated values.
     *
     * @return the sum total of the aggregated values
     */
    public long
    getTotal()
    {
        return total;
    }

    /**
     * Gets the number of aggregated values included in the standard
     * deviation.
     *
     * @return the number of aggregated values included in the standard
     * deviation
     */
    public long
    getCount()
    {
        return count;
    }

    /**
     * Gets the sum total of the squares of the aggregated values.
     *
     * @return the sum total of the squares of the aggregated values
     */
    public BigInteger
    getTotalSquares()
    {
        return totalSquares;
    }

    private void
    readObject(ObjectInputStream s)
            throws IOException, ClassNotFoundException
    {
        s.defaultReadObject();
        // check invariants
        try {
            validate();
        } catch (Exception e) {
            InvalidObjectException x = new InvalidObjectException(
                    e.getMessage());
            x.initCause(e);
            throw x;
        }
    }
}