root/usr/src/cmd/pools/poold/com/sun/solaris/domain/pools/StatisticList.java
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * ident        "%Z%%M% %I%     %E% SMI"
 */

package com.sun.solaris.domain.pools;


import java.util.*;
import java.text.DecimalFormat;

import com.sun.solaris.service.logging.*;

/**
 * Contains information about statistics. An instance must only
 * contain Statistics of the same type.
 */
class StatisticList extends LinkedList
{
        /**
         * The name of the statistic.
         */
        private final String name;

        /**
         * The maximum number of samples to be stored.
         */
        private final int maxSize;

        /**
         * The list of StatisticListeners.
         */
        private List listeners;

        /**
         * Statistically assess utilization.
         */
        private StatisticOperations statisticOperations;

        /**
         * Constructor.
         */
        public StatisticList()
        {
                this("default", 10);
        }

        /**
         * Constructor. Statistics will not be held for this set.
         *
         * @param name is the name of the contained statistics
         * @param size is the maximum number of statistics to hold
         */
        public StatisticList(String name, int size)
        {
                this(name, size, false);
        }

        /**
         * Constructor.
         *
         * @param name is the name of the contained statistics
         * @param size is the maximum number of statistics to hold
         * @param doStats indicates whether or not statistics should
         * be calculated for the data.
         */
        public StatisticList(String name, int size, boolean doStats)
            throws IllegalArgumentException
        {
                super();
                this.name = name;
                if (size < 1)
                        throw new IllegalArgumentException("Size must be > 0");
                this.maxSize = size;
                listeners = new LinkedList();
                if (doStats) {
                        statisticOperations = new StatisticOperations(this);
                        addStatisticListener(statisticOperations);
                }
        }

        /**
         * Return the name of the Statistics being sampled.
         */
        public String getName()
        {
                return (name);
        }

        /**
         * Return a "snapshot" which is the aggregation of all
         * statistic records.
         *
         * @throws NoSuchElementException if there is an error
         * accessing a list member.
         */
        public AggregateStatistic getSnapshot()
            throws NoSuchElementException
        {
                return (getSnapshotForInterval(iterator(), null, null));
        }

        /**
         * Return a "snapshot" of the data using the supplied
         * iterator.
         *
         * @param it An iterator over the contained elements to be
         * used as the basis for the snapshot.
         * @throws NoSuchElementException if there is an error
         * accessing a list member.
         */
        private AggregateStatistic getSnapshot(Iterator it)
            throws NoSuchElementException
        {
                return (getSnapshotForInterval(it, null, null));
        }

        /**
         * Returns the aggregated value for the StatisticList only
         * including samples which satisfy the start and end criteria.
         *
         * @param start start time or null if unspecified.
         * @param end end time or null if unspecified.
         * @throws NoSuchElementException if there is an error
         * accessing a list member.
         */
        public AggregateStatistic getSnapshotForInterval(Date start,
            Date end) throws NoSuchElementException

        {
                return (getSnapshotForInterval(iterator(), start, end));
        }

        /**
         * Returns the aggregated value for the StatisticList only
         * including samples which satisfy the start and end criteria.
         *
         * @param it An iterator over the contained elements to be
         * used as the basis for the snapshot.
         * @param start start time or null if unspecified.
         * @param end end time or null if unspecified.
         * @throws NoSuchElementException if there is an error
         * accessing a list member.
         */
        private AggregateStatistic getSnapshotForInterval(Iterator it,
            Date start, Date end)
        {
                AggregateStatistic f = (AggregateStatistic) getFirst();
                return (f.getSnapshotForInterval(it, start, end));
        }

        /**
         * Add the supplied object to the list. If the list is full,
         * remove the first entry before adding the new entry.
         *
         * @param o Object to add to the list.
         */
        public boolean add(Object o)
        {
                boolean ret;
                if (size() == maxSize)
                        removeFirst();
                ret = super.add(o);
                if (ret)
                        notifyStatisticAdd((AggregateStatistic) o);
                return (ret);
        }

        /**
         * Remove the supplied object from the list.
         *
         * @param o Object to remove from the list.
         */
        public boolean remove(Object o)
        {
                boolean ret;
                ret = super.remove(o);
                if (ret)
                        notifyStatisticRemove((AggregateStatistic) o);
                return (ret);
        }

        /**
         * Removes and returns the first element from this list.
         *
         * @return the first element from this list.
         * @throws        NoSuchElementException if this list is empty.
         */
        public Object removeFirst() {
                Object first = getFirst();
                remove(first);
                return (first);
        }

        /**
         * Add a listener for StatisticEvents.
         *
         * @param l Listener to add.
         */
        public void addStatisticListener(StatisticListener l) {
                listeners.add(l);
        }

        /**
         * Remove a listener for StatisticEvents.
         *
         * @param l Listener to remove.
         */
        public void removeStatisticListener(StatisticListener l) {
                listeners.remove(l);
        }

        /**
         * Notify all StatisticEvent listeners of a new Add event.
         *
         * @param s Event payload.
         */
        private void notifyStatisticAdd(AggregateStatistic s)
        {
                StatisticEvent e = new StatisticEvent(this,
                    StatisticEvent.ADD, s);

                Iterator listIt = listeners.iterator();

                while (listIt.hasNext()) {

                        StatisticListener l = (StatisticListener)listIt.next();
                        l.onStatisticAdd(e);
                }
        }

        /**
         * Notify all StatisticEvent listeners of a new Remove event.
         *
         * @param s Event payload.
         */
        private void notifyStatisticRemove(AggregateStatistic s)
        {
                StatisticEvent e = new StatisticEvent(this,
                    StatisticEvent.REMOVE, s);

                Iterator listIt = listeners.iterator();

                while (listIt.hasNext()) {

                        StatisticListener l = (StatisticListener)listIt.next();
                        l.onStatisticRemove(e);
                }
        }

        /**
         * Return true if the contents of the instance are
         * statistically valid.
         */
        boolean isValid()
        {
                return (statisticOperations.isValid());
        }

        /**
         * Return the zone of control to which the supplied val
         * belongs based on the target details in the supplied
         * objective expression.
         *
         * @param kve Objective expression used to determine zone
         * details.
         * @param val The value to be assessed.
         */
        int getZone(KVOpExpression kve, double val)
        {
                return (statisticOperations.getZone(kve, val));
        }

        /**
         * Return the zone of control to which the supplied val
         * belongs based on the mean of the sampled data.
         *
         * @param val The value to be assessed.
         */
        int getZoneMean(double val)
        {
                return (statisticOperations.getZoneMean(val));
        }

        /**
         * Return the difference (gap) between the target utilization
         * expressed in the supplied objective expression and the
         * supplied value.
         *
         * @param kve Objective expression used to determine target
         * utilization details.
         * @param val The value to be assessed.
         */
        double getGap(KVOpExpression kve, double val)
        {
                return (statisticOperations.getGap(kve, val));
        }

        /**
         * Clear all the data from the StatisticList and reset all the
         * statistic counters.
         */
        public void clear()
        {
                if (statisticOperations != null) {
                        removeStatisticListener(statisticOperations);
                        statisticOperations = new StatisticOperations(this);
                        addStatisticListener(statisticOperations);
                }
                super.clear();
        }

        /**
         * Return a string which describes the zones for this set of
         * data.
         *
         * @param kve The expression containing objectives.
         * @param val The value to be assessed against objectives.
         */
        public String toZoneString(KVOpExpression kve, double val)
        {
                return (statisticOperations.toZoneString(kve, val));
        }
}

/**
 * Event class which describes modifications (Add, Remove) to a
 * StatisticList instance.
 */
final class StatisticEvent extends EventObject
{
        /**
         * Identifier for an ADD event.
         */
        public static final int ADD = 0x1;

        /**
         * Identifier for a REMOVE event.
         */
        public static final int REMOVE = 0x2;

        /**
         * The target of the event.
         */
        private final AggregateStatistic target;

        /**
         * The identifier of this event.
         */
        private final int id;

        /**
         * Constructor.
         *
         * @param source The source of the event.
         * @param id The type of the event.
         * @param target The target of the event.
         */
        public StatisticEvent(Object source, int id, AggregateStatistic target)
        {
                super(source);
                this.id = id;
                this.target = target;
        }

        /**
         * Return the target of the event.
         */
        public AggregateStatistic getTarget()
        {
                return (target);
        }

        /**
         * Return the ID (type) of the event.
         */
        public int getID()
        {
                return (id);
        }

        /**
         * Return the source of the event. This is a typesafe
         * alternative to using getSource().
         */
        public StatisticList getStatisticList()
        {
                return ((StatisticList) source);
        }

}

/**
 * The listener interface for receiving statistic events. The class
 * that is interested in processing a statistic event implements this
 * interface, and the object created with that class is registered
 * with a component, using the component's addStatisticListener
 * method. When the statistic event occurs, the relevant method in the
 * listener object is invoked, and the StatisticEvent is passed to it.
 */
interface StatisticListener extends EventListener
{
        /**
         * Invoked when a statistic is added to the source
         * StatisticList.
         *
         * @param e The event.
         */
        public void onStatisticAdd(StatisticEvent e);

        /**
         * Invoked when a statistic is removed from the source
         * StatisticList.
         *
         * @param e The event.
         */
        public void onStatisticRemove(StatisticEvent e);
}

/**
 * This class performs statistical calculations on a source
 * StatisticList. Zones are regions in a set of samples which are set
 * to be at 1, 2 and 3 standard deviations from the mean. ZONEC is
 * closest to the center, with ZONEZ representing the region beyond
 * ZONEA.
 */
class StatisticOperations implements StatisticListener
{
        /**
         * Control zone C.
         */
        public static final int ZONEC = 0x00010;

        /**
         * Control zone B.
         */
        public static final int ZONEB = 0x00100;

        /**
         * Control zone A.
         */
        public static final int ZONEA = 0x01000;

        /**
         * Control zone Z.
         */
        public static final int ZONEZ = 0x10000;

        /**
         * Direction from mean (used to test ZONELT and ZONEGT).
         */
        public static final int ZONET = 0x00001;

        /**
         * Less than the mean.
         */
        public static final int ZONELT = 0x00000;

        /**
         * Greater than the mean.
         */
        public static final int ZONEGT = 0x00001;

        /**
         * The raw statistical data.
         */
        private final StatisticList statistics;

        /**
         * The mean of the samples.
         */
        private double mean;

        /**
         * The standard deviation of the samples.
         */
        private double sd;

        /**
         * The total of the samples.
         */
        private AggregateStatistic total;

        /**
         * Constructs a new StatisticOperations object for working on
         * the given statistic, whose values are in the given
         * (modifiable) data set.
         *
         * @param statistics The statistics to operate on.
         */
        public StatisticOperations(StatisticList statistics)
        {
                this.statistics = statistics;
                total = new DoubleStatistic(Double.valueOf(0.0));
        }

        /**
         * Calculate the standard deviation for the data held in the
         * associated StatisticsList.
         */
        private void calc_sd()
        {
                Iterator it;

                sd = 0;
                it = statistics.iterator();
                while (it.hasNext()) {
                        Double val = (Double)((DoubleStatistic)
                            ((AggregateStatistic)it.next())).getValue();

                        sd += java.lang.Math.pow(val.doubleValue() - mean, 2);
                }
                sd /= statistics.size();
                sd = java.lang.Math.sqrt(sd);
        }

        /**
         * Return a string which describes the zones for this set of
         * data.
         *
         * @param kve The expression containing objectives.
         * @param val The value to be assessed against objectives.
         */
        public String toZoneString(KVOpExpression kve, double val)
        {
                if (isValid()) {
                        DecimalFormat f = new DecimalFormat("00.00");
                        double target = kve.getValue();

                        if (kve.getOp() == KVOpExpression.LT) {
                                target -= 3 * sd;
                        } else if (kve.getOp() == KVOpExpression.GT) {
                                target += 3 * sd;
                        }
                        StringBuffer buf = new StringBuffer();
                        buf.append(kve.toString());
                        buf.append("\nsample = " + statistics.size());
                        buf.append("\n\ttarget: " + f.format(target));
                        buf.append("\n\tvalue: " + f.format(val));
                        buf.append("\n\tsd: " + f.format(sd));
                        buf.append("\n\tZones:");
                        buf.append("\n\t\tC:" + f.format(target - sd));
                        buf.append("-" + f.format(target + sd));
                        buf.append("\n\t\tB:" + f.format(target - 2 * sd));
                        buf.append("-" + f.format(target + 2 * sd));
                        buf.append("\n\t\tA:" + f.format(target - 3 * sd));
                        buf.append("-" + f.format(target + 3 * sd));
                        return (buf.toString());
                } else {
                        return ("Still sampling...");
                }
        }


        /**
         * Return a string which describes this instance.
         */
        public String toString()
        {
                DecimalFormat f = new DecimalFormat("00.00");

                if (isValid()) {
                        return ("sample = " + statistics.size() +
                            "\n\tmean: " + f.format(mean) +
                            "\n\tsd: " + f.format(sd) +
                            "\n\tZones:" +
                            "\n\t\tC:" + f.format(mean - sd) +
                            "-" + f.format(mean + sd) +
                            "\n\t\tB:" + f.format(mean - 2 * sd) +
                            "-" + f.format(mean + 2 * sd) +
                            "\n\t\tA:" + f.format(mean - 3 * sd) +
                            "-" + f.format(mean + 3 * sd));
                } else {
                        return ("Still sampling...");
                }
        }

        /**
         * Return true if the data is normally distributed. This
         * method currently just returns true if the sample size is >=
         * 5. It could be extended to use a test of normality, for
         * instance "Pearson's Chi-Squared Test" or "Shapiro-Wilks W
         * Test".
         */
        public boolean isValid()
        {
                if (statistics.size() >= 5)
                        return (true);
                return (false);
        }

        /**
         * Calculate the statistical values for the associated
         * samples. This method should be called when the sample
         * population changes.
         */
        private final void process()
        {
                mean = ((Double)((DoubleStatistic)total).getValue()).
                    doubleValue() / statistics.size();
                calc_sd();
        }

        /**
         * Return the control zone for the supplied value using the
         * information derived from the monitored statistics and the
         * objective expressed in the supplied objective expression.
         *
         * @param kve The target utilization expression.
         * @param val The value to be evaluated.
         */
        public int getZone(KVOpExpression kve, double val)
        {
                if (!isValid())
                        return (StatisticOperations.ZONEC);

                double target = kve.getValue();

                if (kve.getOp() == KVOpExpression.LT) {
                        target -= 3 * sd;
                } else if (kve.getOp() == KVOpExpression.GT) {
                        target += 3 * sd;
                }

                return (getZone(target, val));
        }

        /**
         * Return the control zone for the supplied value using the
         * information derived from the monitored statistics.
         *
         * @param val The value to be evaluated.
         */
        public int getZoneMean(double val)
        {
                if (!isValid())
                        return (StatisticOperations.ZONEC);

                return (getZone(mean, val));
        }

        /**
         * Return the control zone for the supplied value using the
         * information derived from the supplied target.
         *
         * @param val The value to be evaluated.
         */
        private int getZone(double target, double val)
        {
                if (!isValid())
                        return (StatisticOperations.ZONEC);

                return ((val < target - 3 * sd) ?
                    ZONEZ | ZONELT : (val > target + 3 * sd) ?
                    ZONEZ | ZONEGT : (val < target - 2 * sd) ?
                    ZONEA | ZONELT : (val > target + 2 * sd) ?
                    ZONEA | ZONEGT : (val < target - sd) ?
                    ZONEB | ZONELT : (val > target + sd) ?
                    ZONEB | ZONEGT : (val < target) ?
                    ZONEC | ZONELT : ZONEC | ZONEGT);
        }

        /**
         * Return the difference (gap) between the target utilization
         * expressed in the supplied objective expression and the
         * supplied value.
         *
         * @param kve Objective expression used to determine target
         * utilization details.
         * @param val The value to be assessed.
         */
        public double getGap(KVOpExpression kve, double val)
        {
                if (!isValid())
                        return (0.0);

                double target = kve.getValue();

                if (kve.getOp() == KVOpExpression.LT) {
                        target -= 3 * sd;
                } else if (kve.getOp() == KVOpExpression.GT) {
                        target += 3 * sd;
                }
                if (val - target < -100)
                        return (-100);
                else if (val - target > 100)
                        return (100);
                else
                        return (val - target);
        }

        /**
         * Event handler for added statistics.
         *
         * @param e The event.
         */
        public void onStatisticAdd(StatisticEvent e)
        {
                total = total.add(e.getTarget());
                process();

        }

        /**
         * Event handler for removed statistics.
         *
         * @param e The event.
         */
        public void onStatisticRemove(StatisticEvent e)
        {
                total = total.subtract(e.getTarget());
                process();
        }
}