001 package org.hd.d.pg2k.ai.scorer;
002
003
004 import java.io.IOException;
005 import java.util.Set;
006
007 import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
008 import org.hd.d.pg2k.svrCore.ExhibitName;
009 import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
010 import org.hd.d.pg2k.svrCore.ExhibitThumbnails;
011 import org.hd.d.pg2k.svrCore.MemoryTools;
012 import org.hd.d.pg2k.svrCore.Name;
013 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
014 import org.hd.d.pg2k.svrCore.SimpleLoggerIF;
015 import org.hd.d.pg2k.svrCore.Tuple.Pair;
016 import org.hd.d.pg2k.svrCore.datasource.SimpleExhibitPipelineIF;
017 import org.hd.d.pg2k.svrCore.vars.EventPeriod;
018
019 /**Shared abstract base to handle common Scorer cache tasks.
020 * Package-visible only for now to support initial cache implementations.
021 *
022 * @author dhd
023 */
024 abstract class AbstractScorerCache implements ScorerCacheIF
025 {
026 protected AbstractScorerCache(final ScorerPopulation population,
027 final SimpleExhibitPipelineIF dataSource,
028 final SimpleLoggerIF log)
029 {
030 if((population == null) || (log == null) || (dataSource == null))
031 { throw new IllegalArgumentException(); }
032 this.log = log;
033 this.population = population;
034 this.dataSource = dataSource;
035
036 // We set up internal caches with capped sizes
037 // so as to concentrate memory usage on the population;
038 // the internal caches are scaled in proportion.
039
040 // We also fold in the heap capacity as a consideration.
041
042 // Limit the the raw score cache in proportion to the population size.
043 final int rMax = Math.max(2 * population.maxSize(), 2 * ScorerCreator.SUGGESTED_CALIB_SET_SIZE);
044 // Have a reduced lower limit to be whittled down to when memory is short.
045 // Experience suggests that a 1GB heap (2^30) should allow ~4k entries (2^12).
046 final int rMin = Math.max(1+ScorerCreator.MIN_SUGGESTED_CALIB_SET_SIZE,
047 (int) Math.min(rMax/4, Runtime.getRuntime().totalMemory() >> 18));
048 _rawScores = MemoryTools.SimpleLRUMapAutoSizeForHitRate.<Pair<String,Name.ExhibitFull>, Pair<Long, ScoreAndConf>>create(rMin, rMax, "_rawScores");
049 }
050
051 /**Logger; never null. */
052 protected final SimpleLoggerIF log;
053 /**Scorer population (and cache of Scorer "goodness"); never null. */
054 protected final ScorerPopulation population;
055 /**Live data source for variables, exhibit data and metadata, etc; never null. */
056 protected final SimpleExhibitPipelineIF dataSource;
057
058 /**Make internal dataSource available to classes in the same package only; never null. */
059 SimpleExhibitPipelineIF getDataSource() { return(dataSource); }
060
061 /**Get current population size; non-negative. */
062 public int size() { return(population.size()); }
063
064 /**Make population available to classes in the same package only; never null. */
065 ScorerPopulation getPopulation() { return(population); }
066
067 /**Poll to do incremental work; does nothing by default. */
068 public void poll() throws IOException { }
069
070 /**Save work-in-progress if possible, and free up resources, ASAP.
071 * This may enable us to reduce work lost during a graceful system shutdown,
072 * but many shutdowns may not be graceful and so we should incrementally save/checkpoint too.
073 * <p>
074 * By default does nothing.
075 */
076 public void destroy() { }
077
078 /**Current set of best-available Scorers with their parameters; never null but may be empty.
079 * This operation should always be relatively fast.
080 */
081 public Set<String> getCurrentScorersWithParameters(final boolean allowStale)
082 { return(population.getCurrentScorersWithParameters(allowStale)); }
083
084 /**Size-limited cache of raw exhibit scores (and when computed); never null.
085 * Map from scorer name-and-parameters and exhibit name to ScoreAndConfidence and time computed.
086 * <p>
087 * This is a thread-safe map, with automatic LRU discarding of stale/unused values.
088 * <p>
089 * A lock can be held on this map to make compound operations atomic,
090 * though the lock should be held for as little time as possible
091 * so as to maximise available concurrency.
092 */
093 private final MemoryTools.SimpleLRUMapAutoSizeForHitRate<Pair<String,Name.ExhibitFull>, Pair<Long, ScoreAndConf>> _rawScores;
094
095 /* Inherit javadoc. */
096 public ScoreAndConf computeUnweightedScoreAndConfidence(final ExhibitFull exhibitName, final ScorerIF scorer, final boolean allowStale)
097 throws IOException
098 {
099 if(scorer == null) { throw new IllegalArgumentException(); }
100 if(!ExhibitName.validNameSyntax(exhibitName)) { throw new IllegalArgumentException("exhibit name must be full, not short"); }
101
102 final long startTime = System.currentTimeMillis();
103
104 // Get vote events for all retained full/complete (VLONG) slots.
105 final long currentPeriod = EventPeriod.VLONG.getIntervalNumber(startTime);
106
107 // If the exhibit is not valid/current then return the "no opinion" result.
108 final AllExhibitImmutableData aeid = dataSource.getAllExhibitImmutableData(-1);
109 final ExhibitStaticAttr esa = aeid.getStaticAttr(exhibitName);
110 if(esa == null) { return(ScoreAndConf.NO_OPINION); }
111
112 // We can use a value from cache if the following conditions hold:
113 // 1) The cached value is from the current period
114 // (or the previous period if allowStale is true).
115 // 2) The cached value is newer than the exhibit file date
116 // (and any thumbnails that might be used).
117 // Since we expect thumbnails only to change rarely for a given exhibit,
118 // ie after a thumbnail-generation algorithm change,
119 // we certainly do not need to force thumbnail creation just to get a timestamp.
120 // We DO NOT intern() the name-and-parameters value because it would stress the PermGen.
121 // We assume that the exhibitName is already an intern()ed value.
122 final Pair<String, Name.ExhibitFull> cacheKey = new Pair<String, Name.ExhibitFull>(scorer.getNameAndParameters(), exhibitName);
123 // See what, if anything, is currently in cache.
124 final Pair<Long, ScoreAndConf> cachedValue = _rawScores.get(cacheKey);
125 final ExhibitThumbnails tns;
126 if((cachedValue != null) &&
127 ((EventPeriod.VLONG.getIntervalNumber(cachedValue.first) >= (allowStale ? currentPeriod-1 : currentPeriod)) &&
128 (cachedValue.first >= esa.timestamp) &&
129 (allowStale || (null == (tns = dataSource.getThumbnails(exhibitName, false))) || (cachedValue.first >= tns.created))))
130 { return(cachedValue.second); }
131
132 // Compute value from scratch.
133 final ScoreAndConf result = scorer.computeScoreAndConfidence(dataSource, exhibitName);
134
135 // Atomically replace the value in cache
136 // making sure that we never replace a newer value.
137 final Pair<Long, ScoreAndConf> newValue = new Pair<Long, ScoreAndConf>(startTime, result);
138 synchronized(_rawScores)
139 {
140 final Pair<Long, ScoreAndConf> curValue = _rawScores.get(cacheKey);
141 if((curValue == null) || (curValue.first < newValue.first))
142 { _rawScores.put(cacheKey, newValue); }
143 }
144
145 return(result);
146 }
147
148 /**Default implementation is uncached; always returns null. */
149 public ScoreAndConf getCachedCompositeScoreAndConfidence(final ExhibitFull exhibitName, final boolean allowStale) throws IOException
150 { return(null); }
151
152 /* (non-Javadoc)
153 * @see org.hd.d.pg2k.ai.scorer.ScorerCacheIF#computeScorerWeighting(org.hd.d.pg2k.ai.scorer.ScorerIF, boolean, java.lang.String)
154 */
155 public ScoreAndConf computeScorerWeighting(final String scorerNameAndParameters, final boolean allowStale, final String source) throws IOException
156 {
157 // Check that the scorer requested is available/valid.
158 // If not, return the "no opinion" result.
159 final ScorerIF scorer = getScorerInstance(scorerNameAndParameters);
160 if(scorer == null) { return(ScoreAndConf.NO_OPINION); }
161 return(computeScorerWeighting(scorer, allowStale, source));
162 }
163
164 /* (non-Javadoc)
165 * @see org.hd.d.pg2k.ai.scorer.ScorerCacheIF#getScorerInstance(java.lang.String)
166 */
167 public ScorerIF getScorerInstance(final String nameAndParameters)
168 {
169 // Use the available functionality in the population instance.
170 return(population.getScorerInstance(nameAndParameters));
171 }
172
173 /**Non-blocking attempt to queue an externally-supplied Scorer value; returns true if accepted.
174 * This default implementation always returns false.
175 */
176 public boolean offerExternalScorer(final String externalScorerNameAndParameters) { return(false); }
177
178 /**Returns true if this cache can accept (many) more external-supplied Scorer values.
179 * This default implementation always returns false.
180 */
181 public boolean canAcceptMoreExternalScorers() { return(false); }
182
183 /**Returns true if at least once external Scorer is queued waiting to be processed.
184 * This default implementation always returns false.
185 */
186 public boolean hasQueuedExternalScorer() { return(false); }
187 }