Return-Path: X-Original-To: apmail-lucene-commits-archive@www.apache.org Delivered-To: apmail-lucene-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id BF866E804 for ; Thu, 3 Jan 2013 14:53:20 +0000 (UTC) Received: (qmail 19347 invoked by uid 500); 3 Jan 2013 14:53:20 -0000 Mailing-List: contact commits-help@lucene.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@lucene.apache.org Delivered-To: mailing list commits@lucene.apache.org Received: (qmail 19330 invoked by uid 99); 3 Jan 2013 14:53:19 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 03 Jan 2013 14:53:19 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 03 Jan 2013 14:53:16 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id BFE9C2388AB9; Thu, 3 Jan 2013 14:52:56 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1428372 - in /lucene/dev/trunk/solr: ./ core/src/java/org/apache/solr/handler/ core/src/java/org/apache/solr/util/stats/ core/src/test/org/apache/solr/core/ Date: Thu, 03 Jan 2013 14:52:56 -0000 To: commits@lucene.apache.org From: romseygeek@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130103145256.BFE9C2388AB9@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: romseygeek Date: Thu Jan 3 14:52:55 2013 New Revision: 1428372 URL: http://svn.apache.org/viewvc?rev=1428372&view=rev Log: SOLR-1972: Add extra query stats to RequestHandler Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Clock.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/EWMA.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ExponentiallyDecayingSample.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Histogram.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Meter.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Sample.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Snapshot.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Timer.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/TimerContext.java (with props) lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/UniformSample.java (with props) Modified: lucene/dev/trunk/solr/CHANGES.txt lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java lucene/dev/trunk/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java Modified: lucene/dev/trunk/solr/CHANGES.txt URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=1428372&r1=1428371&r2=1428372&view=diff ============================================================================== --- lucene/dev/trunk/solr/CHANGES.txt (original) +++ lucene/dev/trunk/solr/CHANGES.txt Thu Jan 3 14:52:55 2013 @@ -185,6 +185,10 @@ New Features * SOLR-4230: The new Solr 4 spatial fields now work with the {!geofilt} and {!bbox} query parsers. The score local-param works too. (David Smiley) +* SOLR-1972: Add extra statistics to RequestHandlers - 5 & 15-minute reqs/sec + rolling averages; median, 75th, 95th, 99th, 99.9th percentile request times + (Alan Woodward, Shawn Heisey, Adrien Grand, Uwe Schindler) + Optimizations ---------------------- Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java?rev=1428372&r1=1428371&r2=1428372&view=diff ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java (original) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java Thu Jan 3 14:52:55 2013 @@ -28,28 +28,30 @@ import org.apache.solr.request.SolrReque import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.search.SyntaxError; import org.apache.solr.util.SolrPluginUtils; +import org.apache.solr.util.stats.Snapshot; +import org.apache.solr.util.stats.Timer; +import org.apache.solr.util.stats.TimerContext; import java.net.URL; +import java.util.concurrent.atomic.AtomicLong; /** * */ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean { - // statistics - // TODO: should we bother synchronizing these, or is an off-by-one error - // acceptable every million requests or so? - volatile long numRequests; - volatile long numErrors; - volatile long numTimeouts; protected NamedList initArgs = null; protected SolrParams defaults; protected SolrParams appends; protected SolrParams invariants; - volatile long totalTime = 0; - long handlerStart = System.currentTimeMillis(); protected boolean httpCaching = true; + // Statistics + private final AtomicLong numRequests = new AtomicLong(); + private final AtomicLong numErrors = new AtomicLong(); + private final AtomicLong numTimeouts = new AtomicLong(); + private final Timer requestTimes = new Timer(); + private final long handlerStart = System.currentTimeMillis(); /** * Initializes the {@link org.apache.solr.request.SolrRequestHandler} by creating three {@link org.apache.solr.common.params.SolrParams} named. @@ -94,7 +96,7 @@ public abstract class RequestHandlerBase public void init(NamedList args) { initArgs = args; - // Copied from StandardRequestHandler + // Copied from StandardRequestHandler if( args != null ) { Object o = args.get("defaults"); if (o != null && o instanceof NamedList) { @@ -114,6 +116,7 @@ public abstract class RequestHandlerBase Object caching = initArgs.get("httpCaching"); httpCaching = caching != null ? Boolean.parseBoolean(caching.toString()) : true; } + } public NamedList getInitArgs() { @@ -124,7 +127,8 @@ public abstract class RequestHandlerBase @Override public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) { - numRequests++; + numRequests.incrementAndGet(); + TimerContext timer = requestTimes.time(); try { SolrPluginUtils.setDefaults(req,defaults,appends,invariants); rsp.setHttpCaching(httpCaching); @@ -135,7 +139,7 @@ public abstract class RequestHandlerBase Object partialResults = header.get("partialResults"); boolean timedOut = partialResults == null ? false : (Boolean)partialResults; if( timedOut ) { - numTimeouts++; + numTimeouts.incrementAndGet(); rsp.setHttpCaching(false); } } @@ -156,11 +160,12 @@ public abstract class RequestHandlerBase } rsp.setException(e); - numErrors++; + numErrors.incrementAndGet(); + } + finally { + timer.stop(); } - totalTime += rsp.getEndTime() - req.getStartTime(); } - //////////////////////// SolrInfoMBeans methods ////////////////////// @@ -192,13 +197,21 @@ public abstract class RequestHandlerBase @Override public NamedList getStatistics() { NamedList lst = new SimpleOrderedMap(); + Snapshot snapshot = requestTimes.getSnapshot(); lst.add("handlerStart",handlerStart); - lst.add("requests", numRequests); - lst.add("errors", numErrors); - lst.add("timeouts", numTimeouts); - lst.add("totalTime",totalTime); - lst.add("avgTimePerRequest", (float) totalTime / (float) this.numRequests); - lst.add("avgRequestsPerSecond", (float) numRequests*1000 / (float)(System.currentTimeMillis()-handlerStart)); + lst.add("requests", numRequests.longValue()); + lst.add("errors", numErrors.longValue()); + lst.add("timeouts", numTimeouts.longValue()); + lst.add("totalTime", requestTimes.getSum()); + lst.add("avgRequestsPerSecond", requestTimes.getMeanRate()); + lst.add("5minRateReqsPerSecond", requestTimes.getFiveMinuteRate()); + lst.add("15minRateReqsPerSecond", requestTimes.getFifteenMinuteRate()); + lst.add("avgTimePerRequest", requestTimes.getMean()); + lst.add("medianRequestTime", snapshot.getMedian()); + lst.add("75thPcRequestTime", snapshot.get75thPercentile()); + lst.add("95thPcRequestTime", snapshot.get95thPercentile()); + lst.add("99thPcRequestTime", snapshot.get99thPercentile()); + lst.add("999thPcRequestTime", snapshot.get999thPercentile()); return lst; } Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Clock.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Clock.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Clock.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Clock.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; + +/** + * An abstraction for how time passes. It is passed to {@link Timer} to track timing. + */ +public abstract class Clock { + /** + * Returns the current time tick. + * + * @return time tick in nanoseconds + */ + public abstract long getTick(); + + /** + * Returns the current time in milliseconds. + * + * @return time in milliseconds + */ + public long getTime() { + return System.currentTimeMillis(); + } + + private static final Clock DEFAULT = new UserTimeClock(); + + /** + * The default clock to use. + * + * @return the default {@link Clock} instance + * + * @see UserTimeClock + */ + public static Clock defaultClock() { + return DEFAULT; + } + + + /** + * A clock implementation which returns the current time in epoch nanoseconds. + */ + public static class UserTimeClock extends Clock { + @Override + public long getTick() { + return System.nanoTime(); + } + } + + /** + * A clock implementation which returns the current thread's CPU time. + */ + public static class CpuTimeClock extends Clock { + private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean(); + + @Override + public long getTick() { + return THREAD_MX_BEAN.getCurrentThreadCpuTime(); + } + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/EWMA.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/EWMA.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/EWMA.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/EWMA.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static java.lang.Math.exp; + +/** + * An exponentially-weighted moving average. + * + * @see UNIX Load Average Part 1: How + * It Works + * @see UNIX Load Average Part 2: Not + * Your Average Average + */ +public class EWMA { + private static final int INTERVAL = 5; + private static final double SECONDS_PER_MINUTE = 60.0; + private static final int ONE_MINUTE = 1; + private static final int FIVE_MINUTES = 5; + private static final int FIFTEEN_MINUTES = 15; + private static final double M1_ALPHA = 1 - exp(-INTERVAL / SECONDS_PER_MINUTE / ONE_MINUTE); + private static final double M5_ALPHA = 1 - exp(-INTERVAL / SECONDS_PER_MINUTE / FIVE_MINUTES); + private static final double M15_ALPHA = 1 - exp(-INTERVAL / SECONDS_PER_MINUTE / FIFTEEN_MINUTES); + + private volatile boolean initialized = false; + private volatile double rate = 0.0; + + private final AtomicLong uncounted = new AtomicLong(); + private final double alpha, interval; + + /** + * Creates a new EWMA which is equivalent to the UNIX one minute load average and which expects + * to be ticked every 5 seconds. + * + * @return a one-minute EWMA + */ + public static EWMA oneMinuteEWMA() { + return new EWMA(M1_ALPHA, INTERVAL, TimeUnit.SECONDS); + } + + /** + * Creates a new EWMA which is equivalent to the UNIX five minute load average and which expects + * to be ticked every 5 seconds. + * + * @return a five-minute EWMA + */ + public static EWMA fiveMinuteEWMA() { + return new EWMA(M5_ALPHA, INTERVAL, TimeUnit.SECONDS); + } + + /** + * Creates a new EWMA which is equivalent to the UNIX fifteen minute load average and which + * expects to be ticked every 5 seconds. + * + * @return a fifteen-minute EWMA + */ + public static EWMA fifteenMinuteEWMA() { + return new EWMA(M15_ALPHA, INTERVAL, TimeUnit.SECONDS); + } + + /** + * Create a new EWMA with a specific smoothing constant. + * + * @param alpha the smoothing constant + * @param interval the expected tick interval + * @param intervalUnit the time unit of the tick interval + */ + public EWMA(double alpha, long interval, TimeUnit intervalUnit) { + this.interval = intervalUnit.toNanos(interval); + this.alpha = alpha; + } + + /** + * Update the moving average with a new value. + * + * @param n the new value + */ + public void update(long n) { + uncounted.addAndGet(n); + } + + /** + * Mark the passage of time and decay the current rate accordingly. + */ + public void tick() { + final long count = uncounted.getAndSet(0); + final double instantRate = count / interval; + if (initialized) { + rate += (alpha * (instantRate - rate)); + } else { + rate = instantRate; + initialized = true; + } + } + + /** + * Returns the rate in the given units of time. + * + * @param rateUnit the unit of time + * @return the rate + */ + public double getRate(TimeUnit rateUnit) { + return rate * (double) rateUnit.toNanos(1); + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ExponentiallyDecayingSample.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ExponentiallyDecayingSample.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ExponentiallyDecayingSample.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/ExponentiallyDecayingSample.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.lang.Math.exp; +import static java.lang.Math.min; + +/** + * An exponentially-decaying random sample of {@code long}s. Uses Cormode et al's forward-decaying + * priority reservoir sampling method to produce a statistically representative sample, + * exponentially biased towards newer entries. + * + * See + * Cormode et al. Forward Decay: A Practical Time Decay Model for Streaming Systems. ICDE '09: Proceedings of the 2009 IEEE International Conference on Data Engineering (2009) + */ +public class ExponentiallyDecayingSample implements Sample { + + private static final long RESCALE_THRESHOLD = TimeUnit.HOURS.toNanos(1); + private final ConcurrentSkipListMap values; + private final ReentrantReadWriteLock lock; + private final double alpha; + private final int reservoirSize; + private final AtomicLong count = new AtomicLong(0); + private volatile long startTime; + private final AtomicLong nextScaleTime = new AtomicLong(0); + private final Clock clock; + // TODO: Maybe replace this with a Mersenne Twister? + private final Random random = new Random(); + + /** + * Creates a new {@link ExponentiallyDecayingSample}. + * + * @param reservoirSize the number of samples to keep in the sampling reservoir + * @param alpha the exponential decay factor; the higher this is, the more biased the + * sample will be towards newer values + */ + public ExponentiallyDecayingSample(int reservoirSize, double alpha) { + this(reservoirSize, alpha, Clock.defaultClock()); + } + + /** + * Creates a new {@link ExponentiallyDecayingSample}. + * + * @param reservoirSize the number of samples to keep in the sampling reservoir + * @param alpha the exponential decay factor; the higher this is, the more biased the + * sample will be towards newer values + */ + public ExponentiallyDecayingSample(int reservoirSize, double alpha, Clock clock) { + this.values = new ConcurrentSkipListMap(); + this.lock = new ReentrantReadWriteLock(); + this.alpha = alpha; + this.reservoirSize = reservoirSize; + this.clock = clock; + clear(); + } + + @Override + public void clear() { + lockForRescale(); + try { + values.clear(); + count.set(0); + this.startTime = currentTimeInSeconds(); + nextScaleTime.set(clock.getTick() + RESCALE_THRESHOLD); + } finally { + unlockForRescale(); + } + } + + @Override + public int size() { + return (int) min(reservoirSize, count.get()); + } + + @Override + public void update(long value) { + update(value, currentTimeInSeconds()); + } + + /** + * Adds an old value with a fixed timestamp to the sample. + * + * @param value the value to be added + * @param timestamp the epoch timestamp of {@code value} in seconds + */ + public void update(long value, long timestamp) { + + rescaleIfNeeded(); + + lockForRegularUsage(); + try { + final double priority = weight(timestamp - startTime) / random.nextDouble(); + final long newCount = count.incrementAndGet(); + if (newCount <= reservoirSize) { + values.put(priority, value); + } else { + Double first = values.firstKey(); + if (first < priority) { + if (values.putIfAbsent(priority, value) == null) { + // ensure we always remove an item + while (values.remove(first) == null) { + first = values.firstKey(); + } + } + } + } + } finally { + unlockForRegularUsage(); + } + + + } + + private void rescaleIfNeeded() { + final long now = clock.getTick(); + final long next = nextScaleTime.get(); + if (now >= next) { + rescale(now, next); + } + } + + @Override + public Snapshot getSnapshot() { + lockForRegularUsage(); + try { + return new Snapshot(values.values()); + } finally { + unlockForRegularUsage(); + } + } + + private long currentTimeInSeconds() { + return TimeUnit.MILLISECONDS.toSeconds(clock.getTime()); + } + + private double weight(long t) { + return exp(alpha * t); + } + + /* "A common feature of the above techniques—indeed, the key technique that + * allows us to track the decayed weights efficiently—is that they maintain + * counts and other quantities based on g(ti − L), and only scale by g(t − L) + * at query time. But while g(ti −L)/g(t−L) is guaranteed to lie between zero + * and one, the intermediate values of g(ti − L) could become very large. For + * polynomial functions, these values should not grow too large, and should be + * effectively represented in practice by floating point values without loss of + * precision. For exponential functions, these values could grow quite large as + * new values of (ti − L) become large, and potentially exceed the capacity of + * common floating point types. However, since the values stored by the + * algorithms are linear combinations of g values (scaled sums), they can be + * rescaled relative to a new landmark. That is, by the analysis of exponential + * decay in Section III-A, the choice of L does not affect the final result. We + * can therefore multiply each value based on L by a factor of exp(−α(L′ − L)), + * and obtain the correct value as if we had instead computed relative to a new + * landmark L′ (and then use this new L′ at query time). This can be done with + * a linear pass over whatever data structure is being used." + */ + private void rescale(long now, long next) { + if (nextScaleTime.compareAndSet(next, now + RESCALE_THRESHOLD)) { + lockForRescale(); + try { + final long oldStartTime = startTime; + this.startTime = currentTimeInSeconds(); + final ArrayList keys = new ArrayList(values.keySet()); + for (Double key : keys) { + final Long value = values.remove(key); + values.put(key * exp(-alpha * (startTime - oldStartTime)), value); + } + + // make sure the counter is in sync with the number of stored samples. + count.set(values.size()); + } finally { + unlockForRescale(); + } + } + } + + private void unlockForRescale() { + lock.writeLock().unlock(); + } + + private void lockForRescale() { + lock.writeLock().lock(); + } + + private void lockForRegularUsage() { + lock.readLock().lock(); + } + + private void unlockForRegularUsage() { + lock.readLock().unlock(); + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Histogram.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Histogram.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Histogram.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Histogram.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import static java.lang.Math.sqrt; + +/** + * A metric which calculates the distribution of a value. + * + * @see Accurately computing running + * variance + */ +public class Histogram { + + private static final int DEFAULT_SAMPLE_SIZE = 1028; + private static final double DEFAULT_ALPHA = 0.015; + + /** + * The type of sampling the histogram should be performing. + */ + enum SampleType { + /** + * Uses a uniform sample of 1028 elements, which offers a 99.9% confidence level with a 5% + * margin of error assuming a normal distribution. + */ + UNIFORM { + @Override + public Sample newSample() { + return new UniformSample(DEFAULT_SAMPLE_SIZE); + } + }, + + /** + * Uses an exponentially decaying sample of 1028 elements, which offers a 99.9% confidence + * level with a 5% margin of error assuming a normal distribution, and an alpha factor of + * 0.015, which heavily biases the sample to the past 5 minutes of measurements. + */ + BIASED { + @Override + public Sample newSample() { + return new ExponentiallyDecayingSample(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA); + } + }; + + public abstract Sample newSample(); + } + + private final Sample sample; + private final AtomicLong min = new AtomicLong(); + private final AtomicLong max = new AtomicLong(); + private final AtomicLong sum = new AtomicLong(); + // These are for the Welford algorithm for calculating running variance + // without floating-point doom. + private final AtomicReference variance = + new AtomicReference(new double[]{-1, 0}); // M, S + private final AtomicLong count = new AtomicLong(); + + /** + * Creates a new {@link Histogram} with the given sample type. + * + * @param type the type of sample to use + */ + Histogram(SampleType type) { + this(type.newSample()); + } + + /** + * Creates a new {@link Histogram} with the given sample. + * + * @param sample the sample to create a histogram from + */ + Histogram(Sample sample) { + this.sample = sample; + clear(); + } + + /** + * Clears all recorded values. + */ + public void clear() { + sample.clear(); + count.set(0); + max.set(Long.MIN_VALUE); + min.set(Long.MAX_VALUE); + sum.set(0); + variance.set(new double[]{ -1, 0 }); + } + + /** + * Adds a recorded value. + * + * @param value the length of the value + */ + public void update(int value) { + update((long) value); + } + + /** + * Adds a recorded value. + * + * @param value the length of the value + */ + public void update(long value) { + count.incrementAndGet(); + sample.update(value); + setMax(value); + setMin(value); + sum.getAndAdd(value); + updateVariance(value); + } + + /** + * Returns the number of values recorded. + * + * @return the number of values recorded + */ + public long getCount() { + return count.get(); + } + + /* (non-Javadoc) + * @see com.yammer.metrics.core.Summarizable#max() + */ + public double getMax() { + if (getCount() > 0) { + return max.get(); + } + return 0.0; + } + + /* (non-Javadoc) + * @see com.yammer.metrics.core.Summarizable#min() + */ + public double getMin() { + if (getCount() > 0) { + return min.get(); + } + return 0.0; + } + + /* (non-Javadoc) + * @see com.yammer.metrics.core.Summarizable#mean() + */ + public double getMean() { + if (getCount() > 0) { + return sum.get() / (double) getCount(); + } + return 0.0; + } + + /* (non-Javadoc) + * @see com.yammer.metrics.core.Summarizable#stdDev() + */ + public double getStdDev() { + if (getCount() > 0) { + return sqrt(getVariance()); + } + return 0.0; + } + + /* (non-Javadoc) + * @see com.yammer.metrics.core.Summarizable#sum() + */ + public double getSum() { + return (double) sum.get(); + } + + public Snapshot getSnapshot() { + return sample.getSnapshot(); + } + + private double getVariance() { + if (getCount() <= 1) { + return 0.0; + } + return variance.get()[1] / (getCount() - 1); + } + + private void setMax(long potentialMax) { + boolean done = false; + while (!done) { + final long currentMax = max.get(); + done = currentMax >= potentialMax || max.compareAndSet(currentMax, potentialMax); + } + } + + private void setMin(long potentialMin) { + boolean done = false; + while (!done) { + final long currentMin = min.get(); + done = currentMin <= potentialMin || min.compareAndSet(currentMin, potentialMin); + } + } + + private void updateVariance(long value) { + while (true) { + final double[] oldValues = variance.get(); + final double[] newValues = new double[2]; + if (oldValues[0] == -1) { + newValues[0] = value; + newValues[1] = 0; + } else { + final double oldM = oldValues[0]; + final double oldS = oldValues[1]; + + final double newM = oldM + ((value - oldM) / getCount()); + final double newS = oldS + ((value - oldM) * (value - newM)); + + newValues[0] = newM; + newValues[1] = newS; + } + if (variance.compareAndSet(oldValues, newValues)) { + return; + } + } + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Meter.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Meter.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Meter.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Meter.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A meter metric which measures mean throughput and one-, five-, and fifteen-minute + * exponentially-weighted moving average throughputs. + * + * @see EMA + */ +public class Meter { + + private static final long TICK_INTERVAL = TimeUnit.SECONDS.toNanos(5); + + private final EWMA m1Rate = EWMA.oneMinuteEWMA(); + private final EWMA m5Rate = EWMA.fiveMinuteEWMA(); + private final EWMA m15Rate = EWMA.fifteenMinuteEWMA(); + + private final AtomicLong count = new AtomicLong(); + private final long startTime; + private final AtomicLong lastTick; + private final TimeUnit rateUnit; + private final String eventType; + private final Clock clock; + + /** + * Creates a new {@link Meter}. + * + * @param eventType the plural name of the event the meter is measuring (e.g., {@code + * "requests"}) + * @param rateUnit the rate unit of the new meter + * @param clock the clock to use for the meter ticks + */ + Meter(String eventType, TimeUnit rateUnit, Clock clock) { + this.rateUnit = rateUnit; + this.eventType = eventType; + this.clock = clock; + this.startTime = this.clock.getTick(); + this.lastTick = new AtomicLong(startTime); + } + + public TimeUnit getRateUnit() { + return rateUnit; + } + + public String getEventType() { + return eventType; + } + + /** + * Updates the moving averages. + */ + void tick() { + m1Rate.tick(); + m5Rate.tick(); + m15Rate.tick(); + } + + /** + * Mark the occurrence of an event. + */ + public void mark() { + mark(1); + } + + /** + * Mark the occurrence of a given number of events. + * + * @param n the number of events + */ + public void mark(long n) { + tickIfNecessary(); + count.addAndGet(n); + m1Rate.update(n); + m5Rate.update(n); + m15Rate.update(n); + } + + private void tickIfNecessary() { + final long oldTick = lastTick.get(); + final long newTick = clock.getTick(); + final long age = newTick - oldTick; + if (age > TICK_INTERVAL && lastTick.compareAndSet(oldTick, newTick)) { + final long requiredTicks = age / TICK_INTERVAL; + for (long i = 0; i < requiredTicks; i++) { + tick(); + } + } + } + + public long getCount() { + return count.get(); + } + + public double getFifteenMinuteRate() { + tickIfNecessary(); + return m15Rate.getRate(rateUnit); + } + + public double getFiveMinuteRate() { + tickIfNecessary(); + return m5Rate.getRate(rateUnit); + } + + public double getMeanRate() { + if (getCount() == 0) { + return 0.0; + } else { + final long elapsed = (clock.getTick() - startTime); + return convertNsRate(getCount() / (double) elapsed); + } + } + + public double getOneMinuteRate() { + tickIfNecessary(); + return m1Rate.getRate(rateUnit); + } + + private double convertNsRate(double ratePerNs) { + return ratePerNs * (double) rateUnit.toNanos(1); + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Sample.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Sample.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Sample.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Sample.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +/** + * A statistically representative sample of a data stream. + */ +public interface Sample { + /** + * Clears all recorded values. + */ + void clear(); + + /** + * Returns the number of values recorded. + * + * @return the number of values recorded + */ + int size(); + + /** + * Adds a new recorded value to the sample. + * + * @param value a new recorded value + */ + void update(long value); + + /** + * Returns a snapshot of the sample's values. + * + * @return a snapshot of the sample's values + */ + Snapshot getSnapshot(); +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Snapshot.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Snapshot.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Snapshot.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Snapshot.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.Arrays; +import java.util.Collection; + +import static java.lang.Math.floor; + +/** + * A statistical snapshot of a {@link Snapshot}. + */ +public class Snapshot { + private static final double MEDIAN_Q = 0.5; + private static final double P75_Q = 0.75; + private static final double P95_Q = 0.95; + private static final double P98_Q = 0.98; + private static final double P99_Q = 0.99; + private static final double P999_Q = 0.999; + + private final double[] values; + + /** + * Create a new {@link Snapshot} with the given values. + * + * @param values an unordered set of values in the sample + */ + public Snapshot(Collection values) { + final Object[] copy = values.toArray(); + this.values = new double[copy.length]; + for (int i = 0; i < copy.length; i++) { + this.values[i] = (Long) copy[i]; + } + Arrays.sort(this.values); + } + + /** + * Create a new {@link Snapshot} with the given values. + * + * @param values an unordered set of values in the sample + */ + public Snapshot(double[] values) { + this.values = new double[values.length]; + System.arraycopy(values, 0, this.values, 0, values.length); + Arrays.sort(this.values); + } + + /** + * Returns the value at the given quantile. + * + * @param quantile a given quantile, in {@code [0..1]} + * @return the value in the distribution at {@code quantile} + */ + public double getValue(double quantile) { + if (quantile < 0.0 || quantile > 1.0) { + throw new IllegalArgumentException(quantile + " is not in [0..1]"); + } + + if (values.length == 0) { + return 0.0; + } + + final double pos = quantile * (values.length + 1); + + if (pos < 1) { + return values[0]; + } + + if (pos >= values.length) { + return values[values.length - 1]; + } + + final double lower = values[(int) pos - 1]; + final double upper = values[(int) pos]; + return lower + (pos - floor(pos)) * (upper - lower); + } + + /** + * Returns the number of values in the snapshot. + * + * @return the number of values in the snapshot + */ + public int size() { + return values.length; + } + + /** + * Returns the median value in the distribution. + * + * @return the median value in the distribution + */ + public double getMedian() { + return getValue(MEDIAN_Q); + } + + /** + * Returns the value at the 75th percentile in the distribution. + * + * @return the value at the 75th percentile in the distribution + */ + public double get75thPercentile() { + return getValue(P75_Q); + } + + /** + * Returns the value at the 95th percentile in the distribution. + * + * @return the value at the 95th percentile in the distribution + */ + public double get95thPercentile() { + return getValue(P95_Q); + } + + /** + * Returns the value at the 98th percentile in the distribution. + * + * @return the value at the 98th percentile in the distribution + */ + public double get98thPercentile() { + return getValue(P98_Q); + } + + /** + * Returns the value at the 99th percentile in the distribution. + * + * @return the value at the 99th percentile in the distribution + */ + public double get99thPercentile() { + return getValue(P99_Q); + } + + /** + * Returns the value at the 99.9th percentile in the distribution. + * + * @return the value at the 99.9th percentile in the distribution + */ + public double get999thPercentile() { + return getValue(P999_Q); + } + + /** + * Returns the entire set of values in the snapshot. + * + * @return the entire set of values in the snapshot + */ + public double[] getValues() { + return Arrays.copyOf(values, values.length); + } + +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Timer.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Timer.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Timer.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/Timer.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import org.apache.solr.util.stats.Histogram.SampleType; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * A timer metric which aggregates timing durations and provides duration statistics, plus + * throughput statistics via {@link Meter}. + */ +public class Timer { + + private final TimeUnit durationUnit, rateUnit; + private final Meter meter; + private final Histogram histogram = new Histogram(SampleType.BIASED); + private final Clock clock; + + public Timer() { + this(TimeUnit.MILLISECONDS, TimeUnit.SECONDS, Clock.defaultClock()); + } + + /** + * Creates a new {@link Timer}. + * + * @param durationUnit the scale unit for this timer's duration metrics + * @param rateUnit the scale unit for this timer's rate metrics + * @param clock the clock used to calculate duration + */ + public Timer(TimeUnit durationUnit, TimeUnit rateUnit, Clock clock) { + this.durationUnit = durationUnit; + this.rateUnit = rateUnit; + this.meter = new Meter("calls", rateUnit, clock); + this.clock = clock; + clear(); + } + + /** + * Returns the timer's duration scale unit. + * + * @return the timer's duration scale unit + */ + public TimeUnit getDurationUnit() { + return durationUnit; + } + + public TimeUnit getRateUnit() { + return rateUnit; + } + + /** + * Clears all recorded durations. + */ + public void clear() { + histogram.clear(); + } + + /** + * Adds a recorded duration. + * + * @param duration the length of the duration + * @param unit the scale unit of {@code duration} + */ + public void update(long duration, TimeUnit unit) { + update(unit.toNanos(duration)); + } + + /** + * Times and records the duration of event. + * + * @param event a {@link Callable} whose {@link Callable#call()} method implements a process + * whose duration should be timed + * @param the type of the value returned by {@code event} + * @return the value returned by {@code event} + * @throws Exception if {@code event} throws an {@link Exception} + */ + public T time(Callable event) throws Exception { + final long startTime = clock.getTick(); + try { + return event.call(); + } finally { + update(clock.getTick() - startTime); + } + } + + /** + * Returns a timing {@link TimerContext}, which measures an elapsed time in nanoseconds. + * + * @return a new {@link TimerContext} + */ + public TimerContext time() { + return new TimerContext(this, clock); + } + + public long getCount() { + return histogram.getCount(); + } + + public double getFifteenMinuteRate() { + return meter.getFifteenMinuteRate(); + } + + public double getFiveMinuteRate() { + return meter.getFiveMinuteRate(); + } + + public double getMeanRate() { + return meter.getMeanRate(); + } + + public double getOneMinuteRate() { + return meter.getOneMinuteRate(); + } + + /** + * Returns the longest recorded duration. + * + * @return the longest recorded duration + */ + public double getMax() { + return convertFromNS(histogram.getMax()); + } + + /** + * Returns the shortest recorded duration. + * + * @return the shortest recorded duration + */ + public double getMin() { + return convertFromNS(histogram.getMin()); + } + + /** + * Returns the arithmetic mean of all recorded durations. + * + * @return the arithmetic mean of all recorded durations + */ + public double getMean() { + return convertFromNS(histogram.getMean()); + } + + /** + * Returns the standard deviation of all recorded durations. + * + * @return the standard deviation of all recorded durations + */ + public double getStdDev() { + return convertFromNS(histogram.getStdDev()); + } + + /** + * Returns the sum of all recorded durations. + * + * @return the sum of all recorded durations + */ + public double getSum() { + return convertFromNS(histogram.getSum()); + } + + public Snapshot getSnapshot() { + final double[] values = histogram.getSnapshot().getValues(); + final double[] converted = new double[values.length]; + for (int i = 0; i < values.length; i++) { + converted[i] = convertFromNS(values[i]); + } + return new Snapshot(converted); + } + + public String getEventType() { + return meter.getEventType(); + } + + private void update(long duration) { + if (duration >= 0) { + histogram.update(duration); + meter.mark(); + } + } + + private double convertFromNS(double ns) { + return ns / TimeUnit.NANOSECONDS.convert(1, durationUnit); + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/TimerContext.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/TimerContext.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/TimerContext.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/TimerContext.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.concurrent.TimeUnit; + +/** + * A timing context. + * + * @see Timer#time() + */ +public class TimerContext { + private final Timer timer; + private final Clock clock; + private final long startTime; + + /** + * Creates a new {@link TimerContext} with the current time as its starting value and with the + * given {@link Timer}. + * + * @param timer the {@link Timer} to report the elapsed time to + */ + TimerContext(Timer timer, Clock clock) { + this.timer = timer; + this.clock = clock; + this.startTime = clock.getTick(); + } + + /** + * Stops recording the elapsed time, updates the timer and returns the elapsed time + */ + public long stop() { + final long elapsedNanos = clock.getTick() - startTime; + timer.update(elapsedNanos, TimeUnit.NANOSECONDS); + return elapsedNanos; + } +} Added: lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/UniformSample.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/UniformSample.java?rev=1428372&view=auto ============================================================================== --- lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/UniformSample.java (added) +++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/util/stats/UniformSample.java Thu Jan 3 14:52:55 2013 @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Forked from https://github.com/codahale/metrics + */ + +package org.apache.solr.util.stats; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; + +/** + * A random sample of a stream of {@code long}s. Uses Vitter's Algorithm R to produce a + * statistically representative sample. + * + * @see Random Sampling with a Reservoir + */ +public class UniformSample implements Sample { + + private static final int BITS_PER_LONG = 63; + private final AtomicLong count = new AtomicLong(); + private final AtomicLongArray values; + //TODO: Maybe replace with a Mersenne twister for better distribution + private static final Random random = new Random(); + + /** + * Creates a new {@link UniformSample}. + * + * @param reservoirSize the number of samples to keep in the sampling reservoir + */ + public UniformSample(int reservoirSize) { + this.values = new AtomicLongArray(reservoirSize); + clear(); + } + + @Override + public void clear() { + for (int i = 0; i < values.length(); i++) { + values.set(i, 0); + } + count.set(0); + } + + @Override + public int size() { + final long c = count.get(); + if (c > values.length()) { + return values.length(); + } + return (int) c; + } + + @Override + public void update(long value) { + final long c = count.incrementAndGet(); + if (c <= values.length()) { + values.set((int) c - 1, value); + } else { + final long r = nextLong(c); + if (r < values.length()) { + values.set((int) r, value); + } + } + } + + /** + * Get a pseudo-random long uniformly between 0 and n-1. Stolen from + * {@link java.util.Random#nextInt()}. + * + * @param n the bound + * @return a value select randomly from the range {@code [0..n)}. + */ + private static long nextLong(long n) { + long bits, val; + do { + bits = random.nextLong() & (~(1L << BITS_PER_LONG)); + val = bits % n; + } while (bits - val + (n - 1) < 0L); + return val; + } + + @Override + public Snapshot getSnapshot() { + final int s = size(); + final List copy = new ArrayList(s); + for (int i = 0; i < s; i++) { + copy.add(values.get(i)); + } + return new Snapshot(copy); + } +} Modified: lucene/dev/trunk/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java?rev=1428372&r1=1428371&r2=1428372&view=diff ============================================================================== --- lucene/dev/trunk/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java (original) +++ lucene/dev/trunk/solr/core/src/test/org/apache/solr/core/RequestHandlersTest.java Thu Jan 3 14:52:55 2013 @@ -18,6 +18,7 @@ package org.apache.solr.core; import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.util.NamedList; import org.apache.solr.handler.StandardRequestHandler; import org.apache.solr.request.SolrRequestHandler; import org.junit.BeforeClass; @@ -87,4 +88,23 @@ public class RequestHandlersTest extends assertNull( core.getRequestHandler("/update/asdgadsgas" ) ); // prefix } + + @Test + public void testStatistics() { + SolrCore core = h.getCore(); + SolrRequestHandler updateHandler = core.getRequestHandler("/update"); + SolrRequestHandler termHandler = core.getRequestHandler("/terms"); + + assertU(adoc("id", "47", + "text", "line up and fly directly at the enemy death cannons, clogging them with wreckage!")); + assertU(commit()); + + NamedList updateStats = updateHandler.getStatistics(); + NamedList termStats = termHandler.getStatistics(); + + Double updateTime = (Double) updateStats.get("totalTime"); + Double termTime = (Double) termStats.get("totalTime"); + + assertFalse("RequestHandlers should not share statistics!", updateTime.equals(termTime)); + } }