package diskCacheV111.poolManager ; import com.google.common.collect.ImmutableMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.regex.Pattern; import diskCacheV111.pools.PoolCostInfo; import diskCacheV111.pools.PoolV2Mode; import diskCacheV111.vehicles.CostModulePoolInfoTable; import diskCacheV111.vehicles.PoolManagerPoolUpMessage; import dmg.cells.nucleus.CellAddressCore; import dmg.cells.nucleus.CellCommandListener; import dmg.cells.nucleus.CellMessage; import dmg.cells.nucleus.CellMessageReceiver; import dmg.cells.nucleus.CellSetupProvider; import dmg.util.command.Argument; import dmg.util.command.Command; import org.dcache.poolmanager.PoolInfo; import org.dcache.util.Args; public class CostModuleV1 implements Serializable, CostModule, CellCommandListener, CellMessageReceiver, CellSetupProvider { private static final Logger LOGGER = LoggerFactory.getLogger(CostModuleV1.class); private static final long serialVersionUID = -267023006449629909L; private final Map<String, Entry> _hash = new HashMap<>() ; private boolean _cachedPercentileCostCutIsValid; private double _cachedPercentileCostCut; private double _cachedPercentileFraction; /** * Information about some specific pool. */ private static class Entry implements Serializable { private static final long serialVersionUID = -6380756950554320179L; private final long timestamp; private final PoolCostInfo _info; private double _fakeCpu = -1.0; private final ImmutableMap<String,String> _tagMap; private final CellAddressCore _address; public Entry(CellAddressCore address, PoolCostInfo info, Map<String,String> tagMap) { timestamp = System.currentTimeMillis(); _address = address; _info = info; _tagMap = (tagMap == null) ? ImmutableMap.of() : ImmutableMap.copyOf(tagMap); } public boolean isValid() { return (System.currentTimeMillis() - timestamp) < 5*60*1000L; } public PoolCostInfo getPoolCostInfo() { return _info; } public ImmutableMap<String, String> getTagMap() { return _tagMap; } public PoolInfo getPoolInfo() { return new PoolInfo(_address, _info, _tagMap); } } public synchronized void messageArrived(CellMessage envelope, PoolManagerPoolUpMessage msg) { CellAddressCore poolAddress = envelope.getSourceAddress(); String poolName = msg.getPoolName(); PoolV2Mode poolMode = msg.getPoolMode(); PoolCostInfo newInfo = msg.getPoolCostInfo(); Entry poolEntry = _hash.get(poolName); boolean isNewPool = poolEntry == null; /* Whether the pool mentioned in the message should be removed */ boolean shouldRemovePool = poolMode.getMode() == PoolV2Mode.DISABLED || poolMode.isDisabled(PoolV2Mode.DISABLED_STRICT) || poolMode.isDisabled(PoolV2Mode.DISABLED_DEAD); if( isNewPool || shouldRemovePool) { _cachedPercentileCostCutIsValid = false; } else { PoolCostInfo currentInfo = poolEntry.getPoolCostInfo(); considerInvalidatingCache(currentInfo, newInfo); } if (shouldRemovePool) { _hash.remove(poolName); } else if (newInfo != null) { _hash.put(poolName, new Entry(poolAddress, newInfo, msg.getTagMap())); } } private void considerInvalidatingCache(PoolCostInfo currentInfo, PoolCostInfo newInfo) { if( !_cachedPercentileCostCutIsValid) { return; } double currentCost = getPerformanceCost(currentInfo); double newCost = getPerformanceCost(newInfo); considerInvalidatingCache(currentCost, newCost); } private void considerInvalidatingCache(double currentCost, PoolCostInfo newInfo) { if( !_cachedPercentileCostCutIsValid) { return; } double newCost = getPerformanceCost(newInfo); considerInvalidatingCache(currentCost, newCost); } /* Check whether we should invalidate the cached. We must do this when * a pool changes its relationship to the cost threshold: * o a pool with cost less than the cached value assumes a cost greater * than the cached value, * o a pool with cost greater than the cached value assumes a cost less * than the cached value. * o a pool with cost equal to the cached value assumes a cost less * than or greater than the cached value. */ private void considerInvalidatingCache(double currentCost, double newCost) { if( Math.signum(currentCost-_cachedPercentileCostCut) != Math.signum(newCost-_cachedPercentileCostCut)) { _cachedPercentileCostCutIsValid = false; } } private double getPerformanceCost(PoolCostInfo info) { return info.getPerformanceCost(); } @Override public synchronized double getPoolsPercentilePerformanceCost(double fraction) { if( fraction <= 0 || fraction >= 1) { throw new IllegalArgumentException("supplied fraction (" + Double.toString( fraction) +") not between 0 and 1"); } if( !_cachedPercentileCostCutIsValid || _cachedPercentileFraction != fraction) { _cachedPercentileCostCut = calculatePercentileCostCut(fraction); _cachedPercentileFraction = fraction; _cachedPercentileCostCutIsValid = true; } return _cachedPercentileCostCut; } private double calculatePercentileCostCut(double fraction) { if( _hash.isEmpty()) { LOGGER.debug("no pools available"); return 0; } LOGGER.debug("{} pools available", _hash.size()); double[] poolCosts = new double[_hash.size()]; int idx=0; for( Entry poolInfo : _hash.values()) { poolCosts[idx] = getPerformanceCost(poolInfo.getPoolCostInfo()); idx++; } Arrays.sort(poolCosts); return poolCosts [ (int) Math.floor(fraction * _hash.size())]; } @Command(name = "cm set debug") @Deprecated public class SetDebugCommand implements Callable<String> { @Argument String value; @Override public String call() throws IllegalArgumentException { return "The 'cm set debug' command is obsolete."; } } @Command(name = "cm set active") @Deprecated public class SetActiveCommand implements Callable<String> { @Argument String value; @Override public String call() throws Exception { return "The 'cm set active' command is obsolete."; } } @Command(name = "cm set update") @Deprecated public class SetUpdateCommand implements Callable<String> { @Argument String value; @Override public String call() throws Exception { return "The 'cm set update' command is obsolete."; } } @Command(name = "cm set magic") @Deprecated public class SetMagicCommand implements Callable<String> { @Argument String value; @Override public String call() throws Exception { return "The 'cm set magic' command is obsolete."; } } public static final String hh_cm_fake = "<poolName> [off] | [-cpu=<cpuCost>|off]" ; public synchronized String ac_cm_fake_$_1_2( Args args ){ String poolName = args.argv(0) ; Entry e = _hash.get(poolName); if( e == null ) { throw new IllegalArgumentException("Pool not found : " + poolName); } if( args.argc() > 1 ){ if( args.argv(1).equals("off") ){ e._fakeCpu = -1.0 ; }else{ throw new IllegalArgumentException("Unknown argument : "+args.argv(1)); } return "Faked Costs switched off for "+poolName ; } String val = args.getOpt("cpu") ; if( val != null ) { e._fakeCpu = Double.parseDouble(val); } return poolName+" -cpu="+e._fakeCpu ; } public static final String hh_xcm_ls = ""; public synchronized Object ac_xcm_ls_$_0(Args args) { CostModulePoolInfoTable reply = new CostModulePoolInfoTable(); for (Entry e : _hash.values() ){ reply.addPoolCostInfo(e.getPoolCostInfo().getPoolName(), e.getPoolCostInfo()); } return reply; } public static final String hh_cm_ls = " -t | -r <pattern> # list all pools"; public synchronized String ac_cm_ls_$_0_1(Args args) { StringBuilder sb = new StringBuilder(); boolean useTime = args.hasOption("t"); boolean useReal = args.hasOption("r"); Pattern pattern = (args.argc() == 0) ? null : Pattern.compile(args.argv(0)); for (Entry e : _hash.values()) { PoolCostInfo pool = e.getPoolCostInfo(); String poolName = pool.getPoolName(); if (pattern == null || pattern.matcher(poolName).matches()) { sb.append(pool).append("\n"); if (useReal) { sb.append(poolName).append("={"); if (e.getTagMap() != null) { sb.append("Tag={").append(e.getTagMap()).append("};"); } sb.append(";CC=").append(getPerformanceCost(pool)).append(";"); sb.append("}").append("\n"); } if (useTime) { sb.append(poolName). append("="). append(System.currentTimeMillis() - e.timestamp). append("\n"); } } } return sb.toString(); } @Override public synchronized Collection<PoolCostInfo> getPoolCostInfos() { Collection<PoolCostInfo> costInfos = new ArrayList<>(); for (Entry entry: _hash.values()) { if (entry.isValid()) { costInfos.add(entry.getPoolCostInfo()); } } return costInfos; } @Override @Nullable public synchronized PoolCostInfo getPoolCostInfo(String poolName) { Entry entry = _hash.get(poolName); if (entry != null && entry.isValid()) { return entry.getPoolCostInfo(); } return null; } @Override @Nullable public synchronized PoolInfo getPoolInfo(String pool) { Entry entry = _hash.get(pool); if (entry != null && entry.isValid()) { return entry.getPoolInfo(); } return null; } @Override public synchronized Map<String,PoolInfo> getPoolInfoAsMap(Iterable<String> pools) { Map<String,PoolInfo> map = new HashMap<>(); for (String pool: pools) { Entry entry = _hash.get(pool); if (entry != null && entry.isValid()) { map.put(pool, entry.getPoolInfo()); } } return map; } private synchronized void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); } }