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 */ StatisticList()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 */ StatisticList(String name, int size)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 */ StatisticList(String name, int size, boolean doStats)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 */ getName()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 */ getSnapshot()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 */ getSnapshot(Iterator it)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 */ getSnapshotForInterval(Date start, Date end)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 */ getSnapshotForInterval(Iterator it, Date start, Date end)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 */ add(Object o)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 */ remove(Object o)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 */ removeFirst()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 */ addStatisticListener(StatisticListener l)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 */ removeStatisticListener(StatisticListener l)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 */ notifyStatisticAdd(AggregateStatistic s)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 */ notifyStatisticRemove(AggregateStatistic s)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 */ isValid()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 */ getZone(KVOpExpression kve, double val)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 */ getZoneMean(double val)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 */ getGap(KVOpExpression kve, double val)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 */ clear()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 */ toZoneString(KVOpExpression kve, double val)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 */ StatisticEvent(Object source, int id, AggregateStatistic target)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 */ getTarget()392 public AggregateStatistic getTarget() 393 { 394 return (target); 395 } 396 397 /** 398 * Return the ID (type) of the event. 399 */ getID()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 */ getStatisticList()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 */ onStatisticAdd(StatisticEvent e)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 */ onStatisticRemove(StatisticEvent e)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 */ StatisticOperations(StatisticList statistics)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 */ calc_sd()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 */ toZoneString(KVOpExpression kve, double val)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 */ toString()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 */ isValid()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 */ process()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 */ getZone(KVOpExpression kve, double val)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 */ getZoneMean(double val)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 */ getZone(double target, double val)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 */ getGap(KVOpExpression kve, double val)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 */ onStatisticAdd(StatisticEvent e)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 */ onStatisticRemove(StatisticEvent e)732 public void onStatisticRemove(StatisticEvent e) 733 { 734 total = total.subtract(e.getTarget()); 735 process(); 736 } 737 } 738