SystemSolver.java revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.util.regex.*;
import java.text.DecimalFormat;
import com.sun.solaris.service.locality.*;
import com.sun.solaris.service.logging.*;
import com.sun.solaris.service.pools.*;
import com.sun.solaris.service.exception.*;
/**
* The <code>SystemSolver</code> class implements a dynamic resource
* allocation solver. The Solver takes a configuration and "solves"
* the resource allocation problem.
*
* This class operates upon a configuration which is suppplied during
* initialization. Its responsibilities include:
* <ul>
* <li><p>
* Maintaining decision history
* <li><p>
* Maintaining information about the locality domain
* <li><p>
* Maintaining a map of all elements and their associated set of objectives
* <li><p>
* Identifying objective expressions with a configuration
* </ul>
*/
class SystemSolver implements Solver {
/**
* The default location of this history file.
*/
public static final String DH_FILE_DEF_PATH =
"/var/adm/pool/history";
/**
* The property overriding the default decision history path.
*/
public static final String DH_FILE_PROP_NAME =
"system.poold.history-file";
/**
* The LocalityDomain extracted from the configuration.
*/
private LocalityDomain ldom;
/**
* The objective map used to link all elements with their set
* of objectives.
*/
private Map objMap;
/**
* The pattern used to identify objective expressions.
*/
private static final Pattern p0 = Pattern.compile(";");
/**
* The configuration for which this solver is "solving".
*/
private Configuration conf;
/**
* Monitor providing statistics for this solver.
*/
private final Monitor monitor;
/**
* The decision history maintainer.
*/
private DecisionHistory dh;
/**
* The path to the decision history file.
*/
private String dhPath;
/**
* The number of CPUs on the system.
*/
private int cpuCount;
/**
* The number of locality triggered examinations made.
*/
private int examineCount;
/**
* Constructs a solver which initialises a decision history
* maintainer. The decision history is be used during operation
* to veto historically-poor decisions output from the objective
* function.
*/
SystemSolver(Monitor monitor)
{
/*
* Create a HashMap to store all objectives.
*/
objMap = new HashMap();
this.monitor = monitor;
}
/**
* Initialize the solver for operation upon the supplied
* configuration. Possible objective expressions set upon
* target elements are identified and stored in an objective
* map which associates elements with the set of their
* objectives.
*
* @param conf The configuration to be manipulated.
* @throws PoolsException If the initialization fails.
*/
public void initialize(Configuration conf) throws PoolsException
{
String oString = null;
String exps[];
this.conf = conf;
/*
* Count the CPUs in the system, this is used to
* control locality objective processing in the
* examine() method.
*/
cpuCount = conf.getComponents(null).size();
examineCount = 0;
/*
* Remove any old objectives
*/
objMap.clear();
/*
* Extract any configuration objectives
*/
try {
oString = conf.getStringProperty(
"system.poold.objectives");
if (oString.length() > 0) {
Set oSet = new HashSet();
objMap.put(conf, oSet);
Poold.CONF_LOG.log(Severity.DEBUG,
"adding configuration objective " +
oString);
exps = p0.split(oString);
for (int i = 0; i < exps.length; i++) {
try {
Expression exp =
Expression.valueOf(
exps[i]);
addObjective(oSet, "system",
exp);
} catch (IllegalArgumentException iae) {
Poold.utility.warn(
Poold.CONF_LOG, iae, false);
}
}
}
} catch (PoolsException pe) {
/*
* Ignore as this means there is no objective
* property
*/
}
/*
* Now extract all pset objectives
*/
Value typeProp = new Value("type", "pset");
List valueList = new LinkedList();
valueList.add(typeProp);
Iterator resIt = conf.getResources(valueList).iterator();
typeProp.close();
while (resIt.hasNext()) {
Resource resource = (Resource)resIt.next();
try {
oString = resource.getStringProperty(
"pset.poold.objectives");
if (oString.length() > 0) {
Set oSet = new HashSet();
objMap.put(resource, oSet);
Poold.CONF_LOG.log(Severity.DEBUG,
"adding " +
resource.getStringProperty(
"pset.name") +
" objective \"" + oString + "\"");
exps = p0.split(oString);
for (int i = 0; i < exps.length; i++) {
Expression exp = null;
try {
exp = Expression.
valueOf(exps[i]);
addObjective(oSet,
"pset", exp);
} catch
(IllegalArgumentException e)
{
Poold.utility.warn(
Poold.CONF_LOG, e,
false);
}
}
}
} catch (PoolsException pe) {
continue;
}
}
Poold.CONF_LOG.log(Severity.DEBUG, "objective map: " +
objMap.toString());
/*
* Capture the LocalityDomain details.
*/
if (ldom != null) {
ldom.close();
}
try {
ldom = new LocalityDomain(LocalityDomain.LGRP_VIEW_OS);
} catch (Exception e) {
Poold.utility.die(Poold.OPT_LOG, e);
}
/*
* Load or create the decision history.
*/
String newDhPath;
try {
newDhPath = conf.getStringProperty(
DH_FILE_PROP_NAME);
} catch (PoolsException pe) {
newDhPath = DH_FILE_DEF_PATH;
}
if (!newDhPath.equals(dhPath)) {
try {
dh = DecisionHistory.loadFromFile(newDhPath);
Poold.CONF_LOG.log(Severity.DEBUG,
"loaded history file " + newDhPath);
} catch (Exception e) {
if (!(e instanceof FileNotFoundException)) {
Poold.CONF_LOG.log(Severity.WARNING,
newDhPath +
": contents unusable; ignoring");
Poold.CONF_LOG.log(Severity.DEBUG,
newDhPath + ": contents unusable",
e);
}
/*
* Use current DecisionHistory instead,
* if any.
*/
if (dh == null)
dh = new DecisionHistory();
}
/*
* Try using the new path.
*/
try {
dh.syncToFile(newDhPath);
} catch (Exception e) {
Poold.utility.die(Poold.CONF_LOG,
new PooldException(newDhPath +
": couldn't synchronize history file")
.initCause(e), false);
}
dhPath = newDhPath;
}
}
/**
* Determine if the given resource has non-workload-dependent
* objectives.
* @param elem The element to examine.
*/
private boolean hasNonWorkloadDependentObjectives(Element elem)
{
Set elemObj = (Set)objMap.get(elem);
/*
* This code relies upon the fact that an element with
* no objectives will not have an empty set, but rather
* no value in the objMap.
*/
if (elemObj == null)
return (false);
Iterator elemObjIt = elemObj.iterator();
while (elemObjIt.hasNext())
if (elemObjIt.next() instanceof
WorkloadDependentObjective)
return (false);
return (true);
}
/**
* Determine if the given resource has workload-dependent
* objectives.
* @param elem The element to examine.
*/
private boolean hasWorkloadDependentObjectives(Element elem)
{
Set elemObj = (Set)objMap.get(elem);
/*
* This code relies upon the fact that an element with
* no objectives will not have an empty set, but rather
* no value in the objMap.
*/
if (elemObj == null)
return (false);
Iterator elemObjIt = elemObj.iterator();
while (elemObjIt.hasNext())
if (elemObjIt.next() instanceof
WorkloadDependentObjective)
return (true);
return (false);
}
/**
* Return true if the monitored configuration should be
* reconfigured. All workload-dependent objectives are
* examined to determine if the configuration is still
* satisfying all specified objectives on all elements. If any
* objectives are failing, then this method will return true.
*
* @param mon The monitoring object used to assess objective
* compliance.
*/
public boolean examine(Monitor mon) throws PoolsException,
StaleMonitorException
{
/*
* Take advantage of the guaranteed-valid monitor data
* for measuring the improvement of any past decisions.
*/
dh.expireAndMeasureImprovements(mon);
Iterator objIt = objMap.keySet().iterator();
boolean ret = false;
boolean hasLocalityObjectives = false;
boolean isMonitorValid = true;
/*
* All objectives are examined, even though failure
* could be detected earlier. This is because logging
* of all objectives is required and the information
* about failure can be stored and used during
* solving.
*/
while (objIt.hasNext()) {
Element elem = (Element) objIt.next();
Set elemObj = (Set) objMap.get(elem);
Iterator elemObjIt = elemObj.iterator();
while (elemObjIt.hasNext()) {
Objective obj = (Objective) elemObjIt.next();
Poold.OPT_LOG.log(Severity.DEBUG,
"checking objective " + obj);
if (obj instanceof WorkloadDependentObjective) {
if (isValid()) {
/*
* If any objectives
* are violated, then
* we must
* reconfigure, so
* check them all in
* turn.
*/
ret = ((WorkloadDependentObjective)
obj).examine(conf, this, elem) ||
ret;
} else
isMonitorValid = false;
}
}
/*
* Check if this is the first element, seen in
* this pass, that has locality objectives.
*/
if (!hasLocalityObjectives &&
hasNonWorkloadDependentObjectives(elem))
hasLocalityObjectives = true;
}
if (isMonitorValid == false) {
Poold.MON_LOG.log(Severity.INFO,
"not evaluating workload-dependent objectives " +
"until sufficient statistics are collected");
}
/*
* If we don't have locality objectives, we don't force
* the reexamination. This is controlled by
* hasLocalityObjectives.
*
* So that we don't continually trigger
* reconfiguration examinations for locality
* objectives we stop forcing recalculations when we
* reach cpuCount / 2. This should be enough moves to
* get good locality for those configurations which
* have no WorkloadDependentObjectives.
*/
return (ret || (hasLocalityObjectives && (examineCount++ <
cpuCount / 2)));
}
/**
* Reallocate resources in a configuration to achieve user
* specified objectives. Return true if the configuration has
* been updated, false otherwise.
*
* This method should only be invoked if a previous
* examination of the configuration which is monitored
* indicates that objectives are failing. The monitored
* configuration is re-opened for editing, locking that
* configuration for the duration of this operation.
*
* @throws Exception If the solve fails.
*/
public boolean solve() throws Exception
{
boolean madeMove = false;
/*
* All solving operations must be done in an
* "editable" context, so create a new modifiable
* configuration which is at the same location as the
* monitored configuration
*/
Configuration rwConf = new Configuration(conf.getLocation(),
PoolInternal.PO_RDWR);
try {
/*
* Build a resource set importance map for use
* when propagating pool importance to each
* possible solution. Use the same logic as
* libpool and let a resource take the highest
* importance value from all pools associated
* with the set.
*/
Map resImp = new HashMap();
List poolList = rwConf.getPools(null);
Iterator itPool = poolList.iterator();
while (itPool.hasNext()) {
Pool pool = (Pool) itPool.next();
long newImp = pool.
getLongProperty("pool.importance");
List resList = pool.getResources(null);
Iterator itRes = resList.iterator();
while (itRes.hasNext()) {
Resource res = (Resource) itRes.next();
if (resImp.containsKey(res)) {
Long imp = (Long) resImp.
get(res);
if (newImp > imp.longValue())
resImp.put(res,
new Long(newImp));
} else
resImp.put(res,
new Long(newImp));
}
}
/*
* Consider all possible alternative
* configurations. This list is generated as a
* series of moves. Administrative constraints
* are applied to the moves to prune the list of
* possible configurations.
*/
Value val = new Value("type", "pset");
List valueList = new LinkedList();
valueList.add(val);
List resList = rwConf.getResources(valueList);
val.close();
List donors = getDonors(resList);
List receivers = getRecipients(resList);
Poold.OPT_LOG.log(Severity.DEBUG, "donors: " +
donors);
Poold.OPT_LOG.log(Severity.DEBUG, "receivers: " +
receivers);
Iterator itDonor = donors.iterator();
List moves = new ArrayList();
while (itDonor.hasNext()) {
Resource donor = (Resource) itDonor.next();
List processors = getProcessors(donor);
Poold.OPT_LOG.log(Severity.DEBUG,
"donor processors: " + processors);
Iterator itProcessor = processors.iterator();
while (itProcessor.hasNext()) {
Component cpu = (Component) itProcessor.
next();
Iterator itReceiver = receivers.
iterator();
while (itReceiver.hasNext()) {
Resource receiver =
(Resource) itReceiver.
next();
/*
* Can't move to yourself
*/
if (receiver == donor)
continue;
moves.add(new ComponentMove(
donor, receiver, cpu));
}
}
}
Poold.OPT_LOG.log(Severity.DEBUG,
"potential moves: " + moves);
/*
* Now that we have our alternative configurations,
* score each configuration by applying all objectives
* to each configuration. Hold the scores in the
* score set.
*/
HashSet scores = new HashSet();
Iterator itMoves = moves.iterator();
while (itMoves.hasNext()) {
double totalContrib = 0;
Move move = (Move) itMoves.next();
Iterator objIt = objMap.keySet().iterator();
while (objIt.hasNext()) {
Element elem = (Element) objIt.next();
Set elemObj = (Set) objMap.get(elem);
Iterator elemObjIt = elemObj.iterator();
while (elemObjIt.hasNext()) {
Objective obj =
(Objective)elemObjIt.next();
if (obj instanceof
LocalityObjective)
((LocalityObjective)obj)
.prepare(ldom,
(Resource)elem);
/*
* If the monitor is
* invalid, do not
* process
* WorkloadDependentObjectives
* since they have an
* implicit dependency
* on the monitor
* data.
*/
if (obj instanceof
WorkloadDependentObjective)
if (!isValid())
continue;
double contrib = obj.calculate(
rwConf, move, elem);
if (contrib < -1 || contrib > 1)
throw new
IllegalOFValueException(
"x: " + contrib +
" is invalid, legal " +
"range is -1 <= x <= " +
"1");
/*
* Modify the basic
* score by the
* importance of the
* objective and (if
* appropriate) the
* importance of an
* associated pool.
*/
if (resImp.containsKey(elem)) {
contrib *= ((Long)
resImp.get(elem)).
longValue();
}
totalContrib += contrib *
obj.getExpression().
getImportance();
}
}
Poold.OPT_LOG.log(Severity.DEBUG,
"scored move (" + move + ") " +
totalContrib);
scores.add(new ScoreMove(move, totalContrib));
}
if (scores.size() != 0) {
/*
* Try to find a move to apply which
* yields a positive contribution.
*/
Object scoresArray[] = scores.toArray();
Arrays.sort(scoresArray,
Collections.reverseOrder());
if ((madeMove = processMoves(rwConf,
scoresArray, false)) == false)
madeMove = processMoves(rwConf,
scoresArray, true);
} else
Poold.OPT_LOG.log(Severity.INFO,
"no moves found");
rwConf.close();
Poold.OPT_LOG.log(Severity.DEBUG,
"synchronizing decision history");
dh.syncToFile(dhPath);
} catch (Exception ex) {
rwConf.close();
throw ex;
}
return (madeMove);
}
/*
* Process the supplied array of scored moves, trying to find
* a move to apply. Return true if a move could be applied,
* false otherwise.
*
* @param conf The configuration to be modified.
* @param scores The areay of scored moves to be tried.
* @param ignoreDH Ignore Decision History details.
*/
private boolean processMoves(Configuration rwConf, Object scores[],
boolean ignoreDH) throws PoolsException, StaleMonitorException
{
boolean madeMove = false;
for (int i = 0; i < scores.length; i++) {
ScoreMove move = (ScoreMove) scores[i];
if (move.getScore() <= 0) {
if (ignoreDH)
Poold.OPT_LOG.log(Severity.INFO,
move + " not applied as " +
"benefit not significant");
break;
}
if ((madeMove = applyMove(rwConf, move, ignoreDH)) ==
true)
break;
}
return (madeMove);
}
/*
* Attempt to apply the supplied move to the
* configuration. Return true if the move could be applied,
* false otherwise.
*
* @param conf The configuration to be modified.
* @param move The move to be applied.
* @param ignoreDH Ignore Decision History details.
*/
private boolean applyMove(Configuration conf, ScoreMove move,
boolean ignoreDH)
throws PoolsException, StaleMonitorException
{
boolean madeMove = false;
boolean wdpInvolved = false;
double utilization = 0.0;
Poold.OPT_LOG.log(Severity.DEBUG, "selected " + move);
if (hasWorkloadDependentObjectives(move.getMove().getTo()) ||
hasWorkloadDependentObjectives(conf)) {
Poold.OPT_LOG.log(Severity.DEBUG,
"Attempting to retrieve utilization for:" + move
.getMove().getTo());
utilization = monitor.getUtilization(move
.getMove().getTo());
wdpInvolved = true;
}
/*
* Unless a move can be vetoed (i.e. decision history
* is effective and there are is a workload-dependent
* involved), the move should alwways be applied.
*/
if (ignoreDH || !wdpInvolved || !dh.veto(move.getMove(),
utilization)) {
Poold.OPT_LOG.log(Severity.INFO,
"applying move " + move.getMove());
move.getMove().apply();
ResourceMonitor mon = getMonitor().get(move.getMove().
getFrom());
mon.resetData("utilization");
mon = getMonitor().get(move.getMove().getTo());
mon.resetData("utilization");
try {
Poold.OPT_LOG.log(Severity.DEBUG,
"committing configuration");
conf.commit(0);
try {
if (move.getMove() instanceof
ComponentMove)
if (wdpInvolved)
dh.recordProcessorMove(
(ComponentMove)move
.getMove(),
utilization,
monitor
.getSampleCount());
else
Poold.OPT_LOG.log(
Severity.DEBUG,
"decision not " +
"recorded due to " +
"lack of workload-"
+ "dependent " +
"objectives");
} catch (Exception e) {
Poold.OPT_LOG.log(Severity.INFO,
"couldn't update " +
"decision history (" +
e.toString() + ")");
}
madeMove = true;
} catch (PoolsException pe) {
conf.rollback();
Poold.OPT_LOG.log(Severity.INFO,
"move failed, possibly due to a " +
"bound process in a 1-processor " +
"set");
}
} else {
/*
* Move was vetoed.
*/
if (!ignoreDH && wdpInvolved)
Poold.OPT_LOG.log(Severity.INFO,
move.getMove() + " not applied due to " +
"poor past results");
}
return (madeMove);
}
/**
* Add an objective based on the supplied expression to the
* supplied set of objectives.
*
* @param oSet Set of objectives to be extended
* @param type Type of element to which the expression is applied
* @param exp Expression to be used in the objective
* @throws IllegalArgumentException If a duplicate objective
* is identified or an invalid expression is supplied for this
* type of element
*/
private void addObjective(Set oSet, String type, Expression exp)
throws IllegalArgumentException
{
Objective o = AbstractObjective.getInstance(type, exp);
Poold.CONF_LOG.log(Severity.DEBUG, "parsed objective " + o);
/*
* Check the set of objectives and find contradictions.
*/
Iterator itObjs = oSet.iterator();
while (itObjs.hasNext()) {
Objective other = (Objective) itObjs.next();
if (o.getExpression().contradicts(
other.getExpression()))
throw new IllegalArgumentException(
"contradictory objectives:" + other +
", " + o);
}
if (oSet.add(o) != true)
throw new IllegalArgumentException(
"duplicate objective:" + o);
}
/**
* Return a list of resource sets prepared to receive
* resources
*
* The list consists of all resource setss (of the supplied
* type) whose size is < their max constraint.
*
* @param resList The list of all resource sets from which
* recipients will be chosen
* @throws PoolsException if there is an error manipulation
* the pool resources
*/
private List getRecipients(List resList) throws PoolsException
{
List recipientList = new ArrayList();
long size, max;
for (int i = 0; i < resList.size(); i++) {
Resource res;
res = (Resource)resList.get(i);
String type = res.getStringProperty("type");
size = res.getLongProperty(type+".size");
max = res.getLongProperty(type+".max");
if (size < max)
recipientList.add(res);
}
return (recipientList);
}
/**
* Return a list of resource sets prepared to donate resources
*
* The list consists of all resource sets (of the supplied
* type) whose size (minus the number of pinned resources
* where applicable) is > their min constraint.
*
* @param resList The list of all resource sets from which
* recipients will be chosen
* @throws PoolsException if there is an error manipulation
* the pool resources
*/
private List getDonors(List resList) throws PoolsException
{
List donorList = new ArrayList();
long size, min;
for (int i = 0; i < resList.size(); i++) {
Value bValue;
Resource res;
List pinned;
ArrayList valueList = new ArrayList();
res = (Resource)resList.get(i);
String type = res.getStringProperty("type");
size = res.getLongProperty(type+".size");
bValue = new Value("cpu.pinned", true);
valueList.add(bValue);
pinned = res.getComponents(valueList);
bValue.close();
min = res.getLongProperty(type+".min");
if (pinned.size() > min)
size -= pinned.size() - min;
if (size > min)
donorList.add(res);
}
return (donorList);
}
/**
* Return a list of Processors for the supplied resource.
*
* The list consists of all Processors (excluding the pinned
* Processors) in the set.
*
* @param set The resource for which Processors should be found
* @throws PoolsException if there is an error manipulation
* the pool resources
*/
private List getProcessors(Resource set) throws PoolsException
{
Iterator it = set.getComponents(null).iterator();
List ret = new ArrayList();
while (it.hasNext()) {
Component cpu = (Component) it.next();
try
{
if (cpu.getBoolProperty("cpu.pinned") == false)
ret.add(cpu);
} catch (PoolsException pe)
{
ret.add(cpu);
}
}
return (ret);
}
/**
* Return true if the solver is capable of working with
* statistically valid data.
*/
public boolean isValid()
{
return (monitor.isValid());
}
/**
* Return the monitor used by this solver.
*/
public Monitor getMonitor()
{
return (monitor);
}
/**
* Return the set of objectives associated with the supplied
* element.
*
* @param elem Retrieve objectives for this element.
*/
public Set getObjectives(Element elem)
{
return ((Set)objMap.get(elem));
}
/**
* Holds details about the score of a proposed configuration
* move. Each move must be scored so that they can ranked in
* terms of increasing desirability
*/
static class ScoreMove implements Comparable {
/**
* The move which is being scored.
*/
private final Move m;
/**
* The score of the move.
*/
private final double score;
/**
* Score formatter.
*/
private static final DecimalFormat scoreFormat =
new DecimalFormat("0.00");
/**
* Constructor.
*
* @param m The move under consideration.
* @param score The score of the move.
*/
public ScoreMove(Move m, double score)
{
this.m = m;
this.score = score;
}
public int compareTo(Object o)
{
ScoreMove other = (ScoreMove) o;
return ((score < other.getScore()) ? -1 :
(score > other.getScore()) ? 1 : 0);
}
public String toString()
{
return ("move (" + m + ") score "
+ scoreFormat.format(score));
}
/**
* Return the score.
*/
double getScore()
{
return (score);
}
/**
* Return the move.
*/
Move getMove()
{
return (m);
}
}
}