xref: /illumos-gate/usr/src/cmd/pools/poold/com/sun/solaris/domain/pools/StatisticList.java (revision 1f606c5b8e527ed924f5cbdbbce612887c7dbe32)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * ident	"%Z%%M%	%I%	%E% SMI"
27  */
28 
29 package com.sun.solaris.domain.pools;
30 
31 
32 import java.util.*;
33 import java.text.DecimalFormat;
34 
35 import com.sun.solaris.service.logging.*;
36 
37 /**
38  * Contains information about statistics. An instance must only
39  * contain Statistics of the same type.
40  */
41 class StatisticList extends LinkedList
42 {
43 	/**
44 	 * The name of the statistic.
45 	 */
46 	private final String name;
47 
48 	/**
49 	 * The maximum number of samples to be stored.
50 	 */
51 	private final int maxSize;
52 
53 	/**
54 	 * The list of StatisticListeners.
55 	 */
56 	private List listeners;
57 
58 	/**
59 	 * Statistically assess utilization.
60 	 */
61 	private StatisticOperations statisticOperations;
62 
63 	/**
64 	 * Constructor.
65 	 */
66 	public StatisticList()
67 	{
68 		this("default", 10);
69 	}
70 
71 	/**
72 	 * Constructor. Statistics will not be held for this set.
73 	 *
74 	 * @param name is the name of the contained statistics
75 	 * @param size is the maximum number of statistics to hold
76 	 */
77 	public StatisticList(String name, int size)
78 	{
79 		this(name, size, false);
80 	}
81 
82 	/**
83 	 * Constructor.
84 	 *
85 	 * @param name is the name of the contained statistics
86 	 * @param size is the maximum number of statistics to hold
87 	 * @param doStats indicates whether or not statistics should
88 	 * be calculated for the data.
89 	 */
90 	public StatisticList(String name, int size, boolean doStats)
91 	    throws IllegalArgumentException
92 	{
93 		super();
94 		this.name = name;
95 		if (size < 1)
96 			throw new IllegalArgumentException("Size must be > 0");
97 		this.maxSize = size;
98 		listeners = new LinkedList();
99 		if (doStats) {
100 			statisticOperations = new StatisticOperations(this);
101 			addStatisticListener(statisticOperations);
102 		}
103 	}
104 
105 	/**
106 	 * Return the name of the Statistics being sampled.
107 	 */
108 	public String getName()
109 	{
110 		return (name);
111 	}
112 
113 	/**
114 	 * Return a "snapshot" which is the aggregation of all
115 	 * statistic records.
116 	 *
117 	 * @throws NoSuchElementException if there is an error
118 	 * accessing a list member.
119 	 */
120 	public AggregateStatistic getSnapshot()
121 	    throws NoSuchElementException
122 	{
123 		return (getSnapshotForInterval(iterator(), null, null));
124 	}
125 
126 	/**
127 	 * Return a "snapshot" of the data using the supplied
128 	 * iterator.
129 	 *
130 	 * @param it An iterator over the contained elements to be
131 	 * used as the basis for the snapshot.
132 	 * @throws NoSuchElementException if there is an error
133 	 * accessing a list member.
134 	 */
135 	private AggregateStatistic getSnapshot(Iterator it)
136 	    throws NoSuchElementException
137 	{
138 		return (getSnapshotForInterval(it, null, null));
139 	}
140 
141 	/**
142 	 * Returns the aggregated value for the StatisticList only
143 	 * including samples which satisfy the start and end criteria.
144 	 *
145 	 * @param start start time or null if unspecified.
146 	 * @param end end time or null if unspecified.
147 	 * @throws NoSuchElementException if there is an error
148 	 * accessing a list member.
149 	 */
150 	public AggregateStatistic getSnapshotForInterval(Date start,
151 	    Date end) throws NoSuchElementException
152 
153 	{
154 		return (getSnapshotForInterval(iterator(), start, end));
155 	}
156 
157 	/**
158 	 * Returns the aggregated value for the StatisticList only
159 	 * including samples which satisfy the start and end criteria.
160 	 *
161 	 * @param it An iterator over the contained elements to be
162 	 * used as the basis for the snapshot.
163 	 * @param start start time or null if unspecified.
164 	 * @param end end time or null if unspecified.
165 	 * @throws NoSuchElementException if there is an error
166 	 * accessing a list member.
167 	 */
168 	private AggregateStatistic getSnapshotForInterval(Iterator it,
169 	    Date start, Date end)
170 	{
171 		AggregateStatistic f = (AggregateStatistic) getFirst();
172 		return (f.getSnapshotForInterval(it, start, end));
173 	}
174 
175 	/**
176 	 * Add the supplied object to the list. If the list is full,
177 	 * remove the first entry before adding the new entry.
178 	 *
179 	 * @param o Object to add to the list.
180 	 */
181 	public boolean add(Object o)
182 	{
183 		boolean ret;
184 		if (size() == maxSize)
185 			removeFirst();
186 		ret = super.add(o);
187 		if (ret)
188 			notifyStatisticAdd((AggregateStatistic) o);
189 		return (ret);
190 	}
191 
192 	/**
193 	 * Remove the supplied object from the list.
194 	 *
195 	 * @param o Object to remove from the list.
196 	 */
197 	public boolean remove(Object o)
198 	{
199 		boolean ret;
200 		ret = super.remove(o);
201 		if (ret)
202 			notifyStatisticRemove((AggregateStatistic) o);
203 		return (ret);
204 	}
205 
206 	/**
207 	 * Removes and returns the first element from this list.
208 	 *
209 	 * @return the first element from this list.
210 	 * @throws	  NoSuchElementException if this list is empty.
211 	 */
212 	public Object removeFirst() {
213 		Object first = getFirst();
214 		remove(first);
215 		return (first);
216 	}
217 
218 	/**
219 	 * Add a listener for StatisticEvents.
220 	 *
221 	 * @param l Listener to add.
222 	 */
223 	public void addStatisticListener(StatisticListener l) {
224 		listeners.add(l);
225 	}
226 
227 	/**
228 	 * Remove a listener for StatisticEvents.
229 	 *
230 	 * @param l Listener to remove.
231 	 */
232 	public void removeStatisticListener(StatisticListener l) {
233 		listeners.remove(l);
234 	}
235 
236 	/**
237 	 * Notify all StatisticEvent listeners of a new Add event.
238 	 *
239 	 * @param s Event payload.
240 	 */
241 	private void notifyStatisticAdd(AggregateStatistic s)
242 	{
243 		StatisticEvent e = new StatisticEvent(this,
244 		    StatisticEvent.ADD, s);
245 
246 		Iterator listIt = listeners.iterator();
247 
248 		while (listIt.hasNext()) {
249 
250 			StatisticListener l = (StatisticListener)listIt.next();
251 			l.onStatisticAdd(e);
252 		}
253 	}
254 
255 	/**
256 	 * Notify all StatisticEvent listeners of a new Remove event.
257 	 *
258 	 * @param s Event payload.
259 	 */
260 	private void notifyStatisticRemove(AggregateStatistic s)
261 	{
262 		StatisticEvent e = new StatisticEvent(this,
263 		    StatisticEvent.REMOVE, s);
264 
265 		Iterator listIt = listeners.iterator();
266 
267 		while (listIt.hasNext()) {
268 
269 			StatisticListener l = (StatisticListener)listIt.next();
270 			l.onStatisticRemove(e);
271 		}
272 	}
273 
274 	/**
275 	 * Return true if the contents of the instance are
276 	 * statistically valid.
277 	 */
278 	boolean isValid()
279 	{
280 		return (statisticOperations.isValid());
281 	}
282 
283 	/**
284 	 * Return the zone of control to which the supplied val
285 	 * belongs based on the target details in the supplied
286 	 * objective expression.
287 	 *
288 	 * @param kve Objective expression used to determine zone
289 	 * details.
290 	 * @param val The value to be assessed.
291 	 */
292 	int getZone(KVOpExpression kve, double val)
293 	{
294 		return (statisticOperations.getZone(kve, val));
295 	}
296 
297 	/**
298 	 * Return the zone of control to which the supplied val
299 	 * belongs based on the mean of the sampled data.
300 	 *
301 	 * @param val The value to be assessed.
302 	 */
303 	int getZoneMean(double val)
304 	{
305 		return (statisticOperations.getZoneMean(val));
306 	}
307 
308 	/**
309 	 * Return the difference (gap) between the target utilization
310 	 * expressed in the supplied objective expression and the
311 	 * supplied value.
312 	 *
313 	 * @param kve Objective expression used to determine target
314 	 * utilization details.
315 	 * @param val The value to be assessed.
316 	 */
317 	double getGap(KVOpExpression kve, double val)
318 	{
319 		return (statisticOperations.getGap(kve, val));
320 	}
321 
322 	/**
323 	 * Clear all the data from the StatisticList and reset all the
324 	 * statistic counters.
325 	 */
326 	public void clear()
327 	{
328 		if (statisticOperations != null) {
329 			removeStatisticListener(statisticOperations);
330 			statisticOperations = new StatisticOperations(this);
331 			addStatisticListener(statisticOperations);
332 		}
333 		super.clear();
334 	}
335 
336 	/**
337 	 * Return a string which describes the zones for this set of
338 	 * data.
339 	 *
340 	 * @param kve The expression containing objectives.
341 	 * @param val The value to be assessed against objectives.
342 	 */
343 	public String toZoneString(KVOpExpression kve, double val)
344 	{
345 		return (statisticOperations.toZoneString(kve, val));
346 	}
347 }
348 
349 /**
350  * Event class which describes modifications (Add, Remove) to a
351  * StatisticList instance.
352  */
353 final class StatisticEvent extends EventObject
354 {
355 	/**
356 	 * Identifier for an ADD event.
357 	 */
358 	public static final int ADD = 0x1;
359 
360 	/**
361 	 * Identifier for a REMOVE event.
362 	 */
363 	public static final int REMOVE = 0x2;
364 
365 	/**
366 	 * The target of the event.
367 	 */
368 	private final AggregateStatistic target;
369 
370 	/**
371 	 * The identifier of this event.
372 	 */
373 	private final int id;
374 
375 	/**
376 	 * Constructor.
377 	 *
378 	 * @param source The source of the event.
379 	 * @param id The type of the event.
380 	 * @param target The target of the event.
381 	 */
382 	public StatisticEvent(Object source, int id, AggregateStatistic target)
383 	{
384 		super(source);
385 		this.id = id;
386 		this.target = target;
387 	}
388 
389 	/**
390 	 * Return the target of the event.
391 	 */
392 	public AggregateStatistic getTarget()
393 	{
394 		return (target);
395 	}
396 
397 	/**
398 	 * Return the ID (type) of the event.
399 	 */
400 	public int getID()
401 	{
402 		return (id);
403 	}
404 
405 	/**
406 	 * Return the source of the event. This is a typesafe
407 	 * alternative to using getSource().
408 	 */
409 	public StatisticList getStatisticList()
410 	{
411 		return ((StatisticList) source);
412 	}
413 
414 }
415 
416 /**
417  * The listener interface for receiving statistic events. The class
418  * that is interested in processing a statistic event implements this
419  * interface, and the object created with that class is registered
420  * with a component, using the component's addStatisticListener
421  * method. When the statistic event occurs, the relevant method in the
422  * listener object is invoked, and the StatisticEvent is passed to it.
423  */
424 interface StatisticListener extends EventListener
425 {
426 	/**
427 	 * Invoked when a statistic is added to the source
428 	 * StatisticList.
429 	 *
430 	 * @param e The event.
431 	 */
432 	public void onStatisticAdd(StatisticEvent e);
433 
434 	/**
435 	 * Invoked when a statistic is removed from the source
436 	 * StatisticList.
437 	 *
438 	 * @param e The event.
439 	 */
440 	public void onStatisticRemove(StatisticEvent e);
441 }
442 
443 /**
444  * This class performs statistical calculations on a source
445  * StatisticList. Zones are regions in a set of samples which are set
446  * to be at 1, 2 and 3 standard deviations from the mean. ZONEC is
447  * closest to the center, with ZONEZ representing the region beyond
448  * ZONEA.
449  */
450 class StatisticOperations implements StatisticListener
451 {
452 	/**
453 	 * Control zone C.
454 	 */
455 	public static final int ZONEC = 0x00010;
456 
457 	/**
458 	 * Control zone B.
459 	 */
460 	public static final int ZONEB = 0x00100;
461 
462 	/**
463 	 * Control zone A.
464 	 */
465 	public static final int ZONEA = 0x01000;
466 
467 	/**
468 	 * Control zone Z.
469 	 */
470 	public static final int ZONEZ = 0x10000;
471 
472 	/**
473 	 * Direction from mean (used to test ZONELT and ZONEGT).
474 	 */
475 	public static final int ZONET = 0x00001;
476 
477 	/**
478 	 * Less than the mean.
479 	 */
480 	public static final int ZONELT = 0x00000;
481 
482 	/**
483 	 * Greater than the mean.
484 	 */
485 	public static final int ZONEGT = 0x00001;
486 
487 	/**
488 	 * The raw statistical data.
489 	 */
490 	private final StatisticList statistics;
491 
492 	/**
493 	 * The mean of the samples.
494 	 */
495 	private double mean;
496 
497 	/**
498 	 * The standard deviation of the samples.
499 	 */
500 	private double sd;
501 
502 	/**
503 	 * The total of the samples.
504 	 */
505 	private AggregateStatistic total;
506 
507 	/**
508 	 * Constructs a new StatisticOperations object for working on
509 	 * the given statistic, whose values are in the given
510 	 * (modifiable) data set.
511 	 *
512 	 * @param statistics The statistics to operate on.
513 	 */
514 	public StatisticOperations(StatisticList statistics)
515 	{
516 		this.statistics = statistics;
517 		total = new DoubleStatistic(new Double(0.0));
518 	}
519 
520 	/**
521 	 * Calculate the standard deviation for the data held in the
522 	 * associated StatisticsList.
523 	 */
524 	private void calc_sd()
525 	{
526 		Iterator it;
527 
528 		sd = 0;
529 		it = statistics.iterator();
530 		while (it.hasNext()) {
531 			Double val = (Double)((DoubleStatistic)
532 			    ((AggregateStatistic)it.next())).getValue();
533 
534 			sd += java.lang.Math.pow(val.doubleValue() - mean, 2);
535 		}
536 		sd /= statistics.size();
537 		sd = java.lang.Math.sqrt(sd);
538 	}
539 
540 	/**
541 	 * Return a string which describes the zones for this set of
542 	 * data.
543 	 *
544 	 * @param kve The expression containing objectives.
545 	 * @param val The value to be assessed against objectives.
546 	 */
547 	public String toZoneString(KVOpExpression kve, double val)
548 	{
549 		if (isValid()) {
550 			DecimalFormat f = new DecimalFormat("00.00");
551 			double target = kve.getValue();
552 
553 			if (kve.getOp() == KVOpExpression.LT) {
554 				target -= 3 * sd;
555 			} else if (kve.getOp() == KVOpExpression.GT) {
556 				target += 3 * sd;
557 			}
558 			StringBuffer buf = new StringBuffer();
559 			buf.append(kve.toString());
560 			buf.append("\nsample = " + statistics.size());
561 			buf.append("\n\ttarget: " + f.format(target));
562 			buf.append("\n\tvalue: " + f.format(val));
563 			buf.append("\n\tsd: " + f.format(sd));
564 			buf.append("\n\tZones:");
565 			buf.append("\n\t\tC:" + f.format(target - sd));
566 			buf.append("-" + f.format(target + sd));
567 			buf.append("\n\t\tB:" + f.format(target - 2 * sd));
568 			buf.append("-" + f.format(target + 2 * sd));
569 			buf.append("\n\t\tA:" + f.format(target - 3 * sd));
570 			buf.append("-" + f.format(target + 3 * sd));
571 			return (buf.toString());
572 		} else {
573 			return ("Still sampling...");
574 		}
575 	}
576 
577 
578 	/**
579 	 * Return a string which describes this instance.
580 	 */
581 	public String toString()
582 	{
583 		DecimalFormat f = new DecimalFormat("00.00");
584 
585 		if (isValid()) {
586 			return ("sample = " + statistics.size() +
587 			    "\n\tmean: " + f.format(mean) +
588 			    "\n\tsd: " + f.format(sd) +
589 			    "\n\tZones:" +
590 			    "\n\t\tC:" + f.format(mean - sd) +
591 			    "-" + f.format(mean + sd) +
592 			    "\n\t\tB:" + f.format(mean - 2 * sd) +
593 			    "-" + f.format(mean + 2 * sd) +
594 			    "\n\t\tA:" + f.format(mean - 3 * sd) +
595 			    "-" + f.format(mean + 3 * sd));
596 		} else {
597 			return ("Still sampling...");
598 		}
599 	}
600 
601 	/**
602 	 * Return true if the data is normally distributed. This
603 	 * method currently just returns true if the sample size is >=
604 	 * 5. It could be extended to use a test of normality, for
605 	 * instance "Pearson's Chi-Squared Test" or "Shapiro-Wilks W
606 	 * Test".
607 	 */
608 	public boolean isValid()
609 	{
610 		if (statistics.size() >= 5)
611 			return (true);
612 		return (false);
613 	}
614 
615 	/**
616 	 * Calculate the statistical values for the associated
617 	 * samples. This method should be called when the sample
618 	 * population changes.
619 	 */
620 	private final void process()
621 	{
622 		mean = ((Double)((DoubleStatistic)total).getValue()).
623 		    doubleValue() / statistics.size();
624 		calc_sd();
625 	}
626 
627 	/**
628 	 * Return the control zone for the supplied value using the
629 	 * information derived from the monitored statistics and the
630 	 * objective expressed in the supplied objective expression.
631 	 *
632 	 * @param kve The target utilization expression.
633 	 * @param val The value to be evaluated.
634 	 */
635 	public int getZone(KVOpExpression kve, double val)
636 	{
637 		if (!isValid())
638 			return (StatisticOperations.ZONEC);
639 
640 		double target = kve.getValue();
641 
642 		if (kve.getOp() == KVOpExpression.LT) {
643 			target -= 3 * sd;
644 		} else if (kve.getOp() == KVOpExpression.GT) {
645 			target += 3 * sd;
646 		}
647 
648 		return (getZone(target, val));
649 	}
650 
651 	/**
652 	 * Return the control zone for the supplied value using the
653 	 * information derived from the monitored statistics.
654 	 *
655 	 * @param val The value to be evaluated.
656 	 */
657 	public int getZoneMean(double val)
658 	{
659 		if (!isValid())
660 			return (StatisticOperations.ZONEC);
661 
662 		return (getZone(mean, val));
663 	}
664 
665 	/**
666 	 * Return the control zone for the supplied value using the
667 	 * information derived from the supplied target.
668 	 *
669 	 * @param val The value to be evaluated.
670 	 */
671 	private int getZone(double target, double val)
672 	{
673 		if (!isValid())
674 			return (StatisticOperations.ZONEC);
675 
676 		return ((val < target - 3 * sd) ?
677 		    ZONEZ | ZONELT : (val > target + 3 * sd) ?
678 		    ZONEZ | ZONEGT : (val < target - 2 * sd) ?
679 		    ZONEA | ZONELT : (val > target + 2 * sd) ?
680 		    ZONEA | ZONEGT : (val < target - sd) ?
681 		    ZONEB | ZONELT : (val > target + sd) ?
682 		    ZONEB | ZONEGT : (val < target) ?
683 		    ZONEC | ZONELT : ZONEC | ZONEGT);
684 	}
685 
686 	/**
687 	 * Return the difference (gap) between the target utilization
688 	 * expressed in the supplied objective expression and the
689 	 * supplied value.
690 	 *
691 	 * @param kve Objective expression used to determine target
692 	 * utilization details.
693 	 * @param val The value to be assessed.
694 	 */
695 	public double getGap(KVOpExpression kve, double val)
696 	{
697 		if (!isValid())
698 			return (0.0);
699 
700 		double target = kve.getValue();
701 
702 		if (kve.getOp() == KVOpExpression.LT) {
703 			target -= 3 * sd;
704 		} else if (kve.getOp() == KVOpExpression.GT) {
705 			target += 3 * sd;
706 		}
707 		if (val - target < -100)
708 			return (-100);
709 		else if (val - target > 100)
710 			return (100);
711 		else
712 			return (val - target);
713 	}
714 
715 	/**
716 	 * Event handler for added statistics.
717 	 *
718 	 * @param e The event.
719 	 */
720 	public void onStatisticAdd(StatisticEvent e)
721 	{
722 		total = total.add(e.getTarget());
723 		process();
724 
725 	}
726 
727 	/**
728 	 * Event handler for removed statistics.
729 	 *
730 	 * @param e The event.
731 	 */
732 	public void onStatisticRemove(StatisticEvent e)
733 	{
734 		total = total.subtract(e.getTarget());
735 		process();
736 	}
737 }
738