/* * 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.io.*; import java.util.*; import java.text.SimpleDateFormat; import com.sun.solaris.service.logging.*; import com.sun.solaris.service.pools.*; /** * This class maintains history about previous decisions. It can be * used to ratify that a decision made on the basis of observed behavior * over a limited time was historically shown to not degrade * performance. The class maintains historical data in a history file. * The format of this data is project-private and very likely to change * as the implementation improves. */ public final class DecisionHistory implements Serializable { /** * The number of samples which a decision will be remembered. */ public static final int DECISION_LIFETIME = 256; /** * Map of values of historical decisions. */ private HashMap decisions = new HashMap(); /** * Map of resources to be monitored for improvement in * utilization to the corresponding decision. Maps Resources to * their decision's key string. */ private transient HashMap resourcesAwaitingImprovement = new HashMap(); /** * List of decisions, in order of creation, to manage expiry. */ private transient LinkedList decisionList = new LinkedList(); /** * Constructor. */ public DecisionHistory() { } /** * Record a decision that's been made regarding a processor. * Such a decision is a (cpuid, from-pset-name, to-pset-name, * (from-pset-composition), (to-pset-composition), * original-utilization-class) tuple. */ public void recordProcessorMove(ComponentMove move, double startingUtilization, int sampleCount) throws PoolsException { Decision decision = Decision.forMove(move, startingUtilization); decision.setStartingSampleCount(sampleCount); Object o = decisions.put(decision.getKey(), decision); Poold.OPT_LOG.log(Severity.DEBUG, "recorded decision (" + decision + ")" + (o == null ? "" : " (displaced " + o + ")")); /* * Remember the most-recently-made decision regarding a * resource until the next utilization sample is taken, * so the next solve() may then reocrd the improvement. * If another decision is made regarding this resource, * the previous ones are forgotten, and their * improvement fields are left 0. */ resourcesAwaitingImprovement.put(move.getTo(), decision.getKey()); decisionList.add(decision); } private void recordImprovementWithUtilization(Resource resource, double utilization) { String decisionKey = (String)resourcesAwaitingImprovement.get( resource); if (decisionKey != null) { Decision decision = (Decision)decisions.get( decisionKey); if (decision != null) { decision.setImprovementWithNewUtilization( utilization); Poold.OPT_LOG.log(Severity.DEBUG, resource + " improvement measured for decision " + decision.describe()); } } } /** * A Decision boils down to a tuple describing the resource * configuration involved before and after a particular resource * is moved. We use a textual representation to describe the * decision, by value, to avoid holding references to any of the * actual resources involved. */ private static abstract class Decision implements Serializable { /** * Utilization of the resource before the move was made. */ private double startingUtilization = 0.0; /** * Improvement in utilization (-1..1) after the move was * made, if the determination is made. */ private double improvement = 0.0; /** * Number of times this decision has been reexamined. */ private int usage = 0; /** * Monitor's sample count when the decision was made, * used to expire this decision after DECISION_LIFETIME * samples. */ private int startingSampleCount; /** * Decision's creation time. */ private Date date; /** * Returns a String key for this Decision. */ public abstract String getKey(); /** * Returns a Decision corresponding to a given Move. * @return a Decision corresponding to a given Move. * @throws InvalidArgumentException if there is no * Decision type corresponding to the move. */ public static final Decision forMove(Move move, double startingUtilization) { if (move instanceof ComponentMove) return (new ComponentMoveDecision( (ComponentMove)move, startingUtilization)); else return (null); } private Decision() { date = new Date(); } /** * Invoked after construction, sets the utilization * corresponding to the affected resource before the move * was made. */ public final void setStartingUtilization( double startingUtilization) { this.startingUtilization = startingUtilization; } /** * Invoked after construction, sets the sampleCount from * the monitor, used to expire this decision after * DECISION_LIFETIME samples. */ public void setStartingSampleCount(int sampleCount) { this.startingSampleCount = sampleCount; } /** * sampleCount accessor. */ public int getStartingSampleCount() { return (startingSampleCount); } /** * Stores the improvement, computed in a * subclass-specific way. */ abstract public void setImprovementWithNewUtilization( double newUtilization); /** * Allow subclasses to record the improvement. */ protected void setImprovement(double improvement) { this.improvement = improvement; } /** * Returns the improvement in utilization measured by * the monitor after the move is made. */ public final double getImprovement() { return (improvement); } /** * Returns the utilization corresponding to the affected * resource before the move was made. */ public final double getStartingUtilization() { return (startingUtilization); } public abstract int hashCode(); public abstract boolean equals(Object o); public abstract String toString(); private static final long serialVersionUID = 0x7860687; /** * Number of times this decision has been reexamined. */ public final int getUsage() { return (usage); } private final void incrementUsage() { usage++; } /** * Returns the time this decision was created. */ public final Date getDate() { return date; } /** * Formatter for printing creation date. */ private static SimpleDateFormat dateFormatter; /** * Format for printing creation date. */ private final static String dateFormat = "MMM d kk:mm:ss"; /** * Returns a more comprehensive textual representation * of this devision than toString(). */ public final String describe() { if (dateFormatter == null) dateFormatter = new SimpleDateFormat( dateFormat); return (toString() + " made at " + dateFormatter.format(getDate()) + " with improvement " + getImprovement() + " used " + getUsage() + " times"); } } /** * A Decision affecting the transfer of one CPU between * processor sets. */ private static final class ComponentMoveDecision extends Decision { /** * The CPU Id of the involved CPU. */ private String cpuid; /** * The name of the donating processor set. */ private String fromPsetName; /** * The name of the receiving processor set. */ private String toPsetName; /** * The string representation of the list of CPU IDs * composing the donating set. */ private String fromPsetComposition; /** * The string representation of the list of CPU IDs * composing the receiving set. */ private String toPsetComposition; /** * The number of CPUs in the receiving set, after the * move is made. */ private int toPsetSize; /** * A Decision-subclass-specific utilization group. */ private String utilizationClass; /** * Constructs a ComponentMoveDecision based on the * ComponentMove. * @throws IllegalArgumentException if the ComponentMove * can't be interpreted. */ public ComponentMoveDecision(ComponentMove move, double startingUtilization) throws IllegalArgumentException { try { cpuid = move.getComponents().toString(); fromPsetName = move.getFrom().toString(); toPsetName = move.getTo().toString(); fromPsetComposition = move.getFrom() .getComponents(null).toString(); toPsetComposition = move.getTo() .getComponents(null).toString(); toPsetSize = move.getTo().getComponents(null) .size(); utilizationClass = computeUtilizationClass( startingUtilization); setStartingUtilization(startingUtilization); } catch (PoolsException pe) { throw(IllegalArgumentException)( new IllegalArgumentException().initCause( pe)); } } public String getKey() { StringBuffer sb = new StringBuffer(); sb.append(cpuid); sb.append(", "); sb.append(fromPsetName); sb.append(", "); sb.append(toPsetName); return (sb.toString()); } public void setImprovementWithNewUtilization( double newUtilization) { double sizeRatio = (double)(toPsetSize - 1) / toPsetSize; double expectedUtilization = sizeRatio * getStartingUtilization(); Poold.OPT_LOG.log(Severity.DEBUG, "pset improvement calculation expected " + expectedUtilization + ", got " + newUtilization); setImprovement(newUtilization - expectedUtilization); } public int hashCode() { return (((((cpuid.hashCode() ^ fromPsetName.hashCode()) ^ toPsetName.hashCode()) ^ fromPsetComposition.hashCode()) ^ toPsetComposition.hashCode()) ^ utilizationClass.hashCode()); } public boolean equals(Object o) { if (!(o instanceof ComponentMoveDecision)) return false; else { ComponentMoveDecision cmd = (ComponentMoveDecision)o; return (cpuid.equals(cmd.cpuid) && fromPsetName.equals(cmd.fromPsetName) && toPsetName.equals(cmd.toPsetName) && fromPsetComposition.equals( cmd.fromPsetComposition) && toPsetComposition.equals( cmd.toPsetComposition) && utilizationClass.equals( cmd.utilizationClass)); } } /** * Returns the group that this decision's utilization * falls into. Presently, there is only one group, but * ostensibly decisions will later be grouped (e.g. * into control-zone-wide groups). */ private String computeUtilizationClass( double startingUtilization) { return "I"; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append(cpuid.toString()); sb.append(", "); sb.append(fromPsetName.toString()); sb.append(", "); sb.append(toPsetName.toString()); sb.append(", "); sb.append(fromPsetComposition.toString()); sb.append(", "); sb.append(toPsetComposition.toString()); sb.append(", "); sb.append(utilizationClass.toString()); return (sb.toString()); } private static final long serialVersionUID = 0xf7860687; } /** * Vetoes a Move only if there is a prior decision that showed a * degradation in resource utilization. */ public boolean veto(Move m, double utilization) { Decision current = Decision.forMove(m, utilization); Decision past; if (current != null) { past = (Decision)decisions.get(current.getKey()); if (past != null) past.incrementUsage(); if (past != null && past.getImprovement() < 0.0) { Poold.OPT_LOG.log(Severity.DEBUG, m + " vetoed by decision " + past.describe()); return true; } } return false; } private static final long serialVersionUID = 0xf7860687; /** * Synchronize the decision history with the persistent version. */ public static DecisionHistory loadFromFile(String path) throws IOException, ClassNotFoundException { return (load(new FileInputStream(path))); } /** * Synchronize the persistent decision history with the present * history. */ public void syncToFile(String path) throws IOException { FileOutputStream fos = new FileOutputStream(path); sync(fos); fos.close(); } /** * Synchronize the decision history with the persistent version, * from the given stream. */ public static DecisionHistory load(InputStream is) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(is); DecisionHistory dh = (DecisionHistory)ois.readObject(); return (dh); } /** * Serialize the persistent decision history to the given * stream. */ public void sync(OutputStream os) throws IOException { new ObjectOutputStream(os).writeObject(this); } public String toString() { StringBuffer sb = new StringBuffer(); sb.append(decisions.keySet().size() + " decisions {"); Iterator it = decisions.keySet().iterator(); while (it.hasNext()) { String dk = (String)it.next(); Decision d = (Decision)decisions.get(dk); sb.append("\t("); sb.append(d.describe()); sb.append(")\n"); } sb.append("}"); return (sb.toString()); } /** * Measures the improvement in utilization of any resource for * which a decision was recently made. */ public void expireAndMeasureImprovements(Monitor mon) { /* * Measure the improvement in resources involved in * recent decisions. */ if (mon.isValid()) { for (Iterator it = resourcesAwaitingImprovement. keySet().iterator(); it.hasNext(); ) { Resource res = (Resource)it.next(); try { double utilization = mon. getUtilization(res); recordImprovementWithUtilization(res, utilization); } catch (StaleMonitorException sme) { /* * We can't access the utilization, so * remove the decision. */ String decisionKey = (String) resourcesAwaitingImprovement. get(res); if (decisionKey != null) decisions.remove(decisionKey); } it.remove(); } } /* * Expire decisions which have outlived * DECISION_LIFETIME samples. */ int cutoff = mon.getSampleCount() - DECISION_LIFETIME; if (cutoff > 0) { Decision decision; ListIterator it = decisionList.listIterator(0); while (it.hasNext()) { decision = (Decision)it.next(); int sc = decision.getStartingSampleCount(); if (sc < cutoff) { if (sc > 0) { Poold.OPT_LOG.log( Severity.DEBUG, "expiring decision (" + decision + ")"); it.remove(); decisions.remove( decision.getKey()); } } else break; } } } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); resourcesAwaitingImprovement = new HashMap(); decisionList = new LinkedList(); for (Iterator it = decisions.keySet().iterator(); it.hasNext(); ) { String decisionKey = (String)it.next(); Decision decision = (Decision)decisions.get( decisionKey); decision.setStartingSampleCount(0); decisionList.add(decision); } } }