package org.dcache.services.info.gathers; import java.util.Date; import java.util.Random; import org.dcache.services.info.base.StateComposite; import org.dcache.services.info.base.StatePath; import org.dcache.services.info.base.StateUpdate; import org.dcache.services.info.base.StateUpdateManager; import org.dcache.services.info.stateInfo.SpaceInfo; import static org.dcache.util.ByteUnit.GiB; import static org.dcache.util.ByteUnit.BYTES; /** * The PseudoPoolDga provides a stream of fake data about a fake pool. It * is used to introduce load on the info state system and watchers. This * DGA will also register itself as a member of the <code>default</code> * Poolgroup. * <p> * To use this class, add a line like: * <pre> * // A pool that sends status information every 30s * // (called "pseudo_1" and containing 40 GiB) * addActivity(new PseudoPoolDga("pseudo_1", 30000, 40)); * </pre> * to the <code>DataGatheringScheduler.addDefaultActivity()</code> method. * <p> * This class provides a very rough-and-ready simulation of data activity, * with files being added and deleted at random intervals. * * @author Paul Millar <paul.millar@desy.de> */ public class PseudoPoolDga implements Schedulable { /** Max number standard deviation to allow in delay */ private static final int NUM_SD = 4; /** The safety margin we should ensure for metrics, in seconds */ private static final int SAFETY_MARGIN = 2; private static final StatePath DEFAULT_POOLGROUP_MEMBERSHIP = StatePath.parsePath("poolgroups.default.pools"); static final double PRECIOUS_ADD_LIKELIHOOD = 0.5; /** Fraction of total space to add, on average */ static final double PRECIOUS_ADD_DELTA = 0.01; /** Standard deviation of space added, as fraction of the average */ static final double PRECIOUS_ADD_SPREAD = 0.01; static final double PRECIOUS_DEL_DELTA = 0.1; static final double PRECIOUS_DEL_SPREAD = 0.01; static final double PRECIOUS_DEL_SUPPRESS = 0.01; /** Likelihood of adding files each iteration */ static final double REMOVABLE_ADD_LIKELIHOOD = 0.8; /** Fraction of total space that is max added in Zipf */ static final double REMOVABLE_ADD_MAX_FRAC = 0.03; /** How fast the Zipf falls off, controls the spread of files */ static final double REMOVABLE_ADD_EXP = 0.05; /** When removing, what fraction of removable is deleted*/ static final double REMOVABLE_DEL_DELTA = 0.3; /** The standard deviation of the removed amount, from */ static final double REMOVABLE_DEL_SPREAD = 0.05; /** Suppress how often files are removed */ static final double REMOVABLE_DEL_SUPPRESS = 0.01; private final String _poolName; private final SpaceInfo _spaceInfo; private final StatePath _ourSpacePath; private StatePath _ourPgMembership; /** Average time between successive calls, in milliseconds */ private final int _delay; /** Standard deviation for Normal spread of successive calls */ private final int _spread; /** Lifetime of metrics, in seconds */ private final long _metricLifetime; /** Maximum delay between successive queries, in milliseconds */ private final long _maxDelay; private final Random _rnd = new Random(); private final StateUpdateManager _sum; /** * Create a new fake pool, injecting information roughly periodically * (5% spread) and randomly increasing (and purging) space usage. * @param poolName the name of this pool * @param how often (on average) update metrics should be sent, in milliseconds * @param the total capacity of this pool, in GiB */ public PseudoPoolDga(StateUpdateManager sum, String poolName, int updatePeriod, long capacity) { _poolName = poolName; _sum = sum; StatePath pools = new StatePath("pools"); _ourSpacePath = pools.newChild(poolName).newChild("space"); // Parameters for how often metrics are updated _delay = updatePeriod; _spread = (int) (0.05 * updatePeriod); _maxDelay = _delay + NUM_SD * _spread; _metricLifetime = _maxDelay/1000 + SAFETY_MARGIN; _ourPgMembership = DEFAULT_POOLGROUP_MEMBERSHIP.newChild(poolName); // Initially, completely empty. _spaceInfo = new SpaceInfo(GiB.toBytes(capacity)); } // Roughly periodic: Normal with mean: _duration, SD: _spread @Override public Date shouldNextBeTriggered() { long thisDelay = _delay + (long) (_rnd.nextGaussian() * _spread); if (thisDelay > _maxDelay) { thisDelay = _maxDelay; } return new Date(System.currentTimeMillis() + thisDelay); } /** * When we've been triggered. */ @Override public void trigger() { updateSpace(); StateUpdate update = new StateUpdate(); // Renew our membership of default poolgroup update.appendUpdate(_ourPgMembership, new StateComposite(_metricLifetime)); _spaceInfo.addMetrics(update, _ourSpacePath, _metricLifetime); _sum.enqueueUpdate(update); } /** * Update our space information. We try to provide a half-way reasonable model of how * a pool behaves. */ private void updateSpace() { // People add precious data with fixed likelihood that is mostly the same size. if (_rnd.nextFloat() < PRECIOUS_ADD_LIKELIHOOD) { _spaceInfo.updatePrecious((long) (_spaceInfo .getTotal() * PRECIOUS_ADD_DELTA * (1 + _rnd .nextGaussian() * PRECIOUS_ADD_SPREAD))); } // As pool becomes more full, there's increased likelihood that some files are deleted double freeFrac = _spaceInfo.getFree() / (double)_spaceInfo.getTotal(); if (_rnd.nextDouble() > PRECIOUS_DEL_SUPPRESS*Math.pow(freeFrac, 5)) { long size = (long) (_spaceInfo.getPrecious() * PRECIOUS_DEL_DELTA * (1 + _rnd.nextGaussian() * PRECIOUS_DEL_SPREAD)); _spaceInfo.updatePrecious(-size); } // Likewise with removable, add some data, taken from a Zipf-like distribution. if (_rnd.nextFloat() < REMOVABLE_ADD_LIKELIHOOD) { long size = (long) (_spaceInfo.getTotal()*REMOVABLE_ADD_MAX_FRAC*Math.exp(-_rnd.nextDouble()/REMOVABLE_ADD_EXP)); _spaceInfo.updateRemovable(size); } // Again, people increasingly likely to delete files when space becomes full. if (_rnd.nextDouble() > REMOVABLE_DEL_SUPPRESS*Math.pow(freeFrac, 5)) { long size = (long) (_spaceInfo.getPrecious() * REMOVABLE_DEL_DELTA * (1 + _rnd.nextGaussian() * REMOVABLE_DEL_SPREAD)); _spaceInfo.updatePrecious(-size); } } @Override public String toString() { return this.getClass().getSimpleName() + "[" + _poolName + "]"; } }