/* * 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(new Double(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(); } }