View Javadoc

1   // Copyright (C) 2000 - 2011 Philip Aston
2   // All rights reserved.
3   //
4   // This file is part of The Grinder software distribution. Refer to
5   // the file LICENSE which is part of The Grinder distribution for
6   // licensing details. The Grinder distribution is available on the
7   // Internet at http://grinder.sourceforge.net/
8   //
9   // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
13  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
20  // OF THE POSSIBILITY OF SUCH DAMAGE.
21  
22  package net.grinder.statistics;
23  
24  import java.io.IOException;
25  import java.io.ObjectInput;
26  import java.io.ObjectOutput;
27  import java.util.Arrays;
28  
29  import net.grinder.statistics.StatisticsIndexMap.DoubleIndex;
30  import net.grinder.statistics.StatisticsIndexMap.DoubleSampleIndex;
31  import net.grinder.statistics.StatisticsIndexMap.LongIndex;
32  import net.grinder.statistics.StatisticsIndexMap.LongSampleIndex;
33  import net.grinder.statistics.StatisticsIndexMap.SampleIndex;
34  import net.grinder.util.Serialiser;
35  
36  
37  /**
38   * Store an array of raw statistics as unsigned long or double values. Clients
39   * can access individual values using an index obtained from a {@link
40   * StatisticsIndexMap}.
41   *
42   * @author Philip Aston
43   */
44  final class StatisticsSetImplementation implements StatisticsSet {
45  
46    private final StatisticsIndexMap m_statisticsIndexMap;
47    private final long[] m_longData;
48    private final double[] m_doubleData;
49  
50    // Transient fields are context specific. They are not serialised, nor are
51    // they added to other statistics sets. E.g. the "period" field.
52    private transient long[] m_transientLongData;
53  
54    // true => all statistics are zero; false => they might be.
55    private boolean m_zero = true;
56  
57    private boolean m_composite;
58  
59    /**
60     * Creates a new <code>StatisticsSetImplementation</code> instance.
61     *
62     * @param statisticsIndexMap The {@link StatisticsIndexMap} to use.
63     */
64    StatisticsSetImplementation(StatisticsIndexMap statisticsIndexMap) {
65      m_statisticsIndexMap = statisticsIndexMap;
66      m_longData = new long[m_statisticsIndexMap.getNumberOfLongs()];
67      m_doubleData = new double[m_statisticsIndexMap.getNumberOfDoubles()];
68      m_transientLongData =
69        new long[m_statisticsIndexMap.getNumberOfTransientLongs()];
70    }
71  
72    /**
73     * Reset this StatisticsSet to default values. Allows instance to
74     * be reused.
75     */
76    public synchronized void reset() {
77      if (!m_zero) {
78        Arrays.fill(m_longData, 0);
79        Arrays.fill(m_doubleData, 0);
80        Arrays.fill(m_transientLongData, 0);
81        m_zero = true;
82        m_composite = false;
83      }
84    }
85  
86    /**
87     * Clone this object.
88     *
89     * We don't use {@link Object#clone} as that's such a hog's arse of a
90     * mechanism. It prevents us from using final variables, requires a lot of
91     * casting, and that we catch CloneNotSupported exceptions - even if we know
92     * they won't be thrown.
93     *
94     * @return A copy of this StatisticsSetImplementation.
95     */
96    public synchronized StatisticsSet snapshot() {
97      final StatisticsSetImplementation result =
98        new StatisticsSetImplementation(m_statisticsIndexMap);
99  
100     if (!m_zero) {
101       synchronized (result) {
102         System.arraycopy(m_longData,
103                          0,
104                          result.m_longData,
105                          0,
106                          result.m_longData.length);
107 
108         System.arraycopy(m_doubleData,
109                          0,
110                          result.m_doubleData,
111                          0,
112                          result.m_doubleData.length);
113 
114         System.arraycopy(m_transientLongData,
115                          0,
116                          result.m_transientLongData,
117                          0, result.m_transientLongData.length);
118 
119         result.m_zero = false;
120         result.m_composite = m_composite;
121       }
122     }
123 
124     return result;
125   }
126 
127   /**
128    * Return the value specified by <code>index</code>.
129    *
130    * <p>Unfortunately the Java language specification does not make
131    * long access atomic, so we must synchronise.</p>
132    *
133    * @param index The process specific index.
134    * @return The value.
135    */
136   public synchronized long getValue(LongIndex index) {
137     if (index.isTransient()) {
138       return m_transientLongData[index.getValue()];
139     }
140 
141     return m_longData[index.getValue()];
142   }
143 
144   /**
145    * Return the value specified by <code>index</code>.
146    *
147    * <p>Unfortunately the Java language specification does not make
148    * double access atomic, so we must synchronise.</p>
149    *
150    * @param index The process specific index.
151    * @return The value.
152    */
153   public synchronized double getValue(DoubleIndex index) {
154     return m_doubleData[index.getValue()];
155   }
156 
157   /**
158    * Set the value specified by <code>index</code>.
159    *
160    * <p>Unfortunately the Java language specification does not make
161    * long access atomic, so we must synchronise.</p>
162    *
163    * @param index The process specific index.
164    * @param value The value.
165    */
166   public synchronized void setValue(LongIndex index, long value) {
167     if (index.isTransient()) {
168       m_transientLongData[index.getValue()] = value;
169     }
170     else {
171       m_longData[index.getValue()] = value;
172     }
173 
174     m_zero &= value == 0;
175   }
176 
177   /**
178    * Set the value specified by <code>index</code>.
179    *
180    * <p>Unfortunately the Java language specification does not make
181    * double access atomic, so we must synchronise.</p>
182    *
183    * @param index The process specific index.
184    * @param value The value.
185    */
186   public synchronized void setValue(DoubleIndex index, double value) {
187     m_doubleData[index.getValue()] = value;
188     m_zero &= value == 0;
189   }
190 
191   /**
192    * Add <code>value</code> to the value specified by
193    * <code>index</code>.
194    *
195    * <p>Synchronised to ensure we don't lose information.</p>.
196    *
197    * @param index The process specific index.
198    * @param value The value.
199    */
200   public synchronized void addValue(LongIndex index, long value) {
201     if (!index.isTransient()) {
202       m_longData[index.getValue()] += value;
203       m_zero &= value == 0;
204     }
205   }
206 
207   /**
208    * Add <code>value</code> to the value specified by
209    * <code>index</code>.
210    *
211    * <p>Synchronised to ensure we don't lose information.</p>.
212    *
213    * @param index The process specific index.
214    * @param value The value.
215    */
216   public synchronized void addValue(DoubleIndex index, double value) {
217 
218     m_doubleData[index.getValue()] += value;
219     m_zero &= value == 0;
220   }
221 
222   /**
223    * Add sample <code>value</code> to the sample statistic specified by
224    * <code>index</code>.
225    *
226    * @param index The index.
227    * @param value The value.
228    */
229   public synchronized void addSample(LongSampleIndex index, long value) {
230 
231     setValue(index.getVarianceIndex(),
232         calculateVariance(getValue(index.getSumIndex()),
233                           getValue(index.getCountIndex()),
234                           getValue(index.getVarianceIndex()),
235                           value));
236 
237     m_longData[index.getSumIndex().getValue()] += value;
238     ++m_longData[index.getCountIndex().getValue()];
239     m_zero = false;
240   }
241 
242   /**
243    * Add sample <code>value</code> to the sample statistic specified by
244    * <code>index</code>.
245    *
246    * @param index The index.
247    * @param value The value.
248    */
249   public synchronized void addSample(DoubleSampleIndex index, double value) {
250 
251     setValue(index.getVarianceIndex(),
252         calculateVariance(getValue(index.getSumIndex()),
253                           getValue(index.getCountIndex()),
254                           getValue(index.getVarianceIndex()),
255                           value));
256 
257     m_doubleData[index.getSumIndex().getValue()] += value;
258     ++m_longData[index.getCountIndex().getValue()];
259     m_zero = false;
260   }
261 
262   /**
263    * Reset the sample statistic specified by <code>index</code>.
264    *
265    * @param index
266    */
267   public synchronized void reset(LongSampleIndex index) {
268     setValue(index.getSumIndex(), 0);
269     setValue(index.getCountIndex(), 0);
270     setValue(index.getVarianceIndex(), 0);
271   }
272 
273   /**
274    * Reset the sample statistic specified by <code>index</code>.
275    *
276    * @param index
277    */
278   public synchronized void reset(DoubleSampleIndex index) {
279     setValue(index.getSumIndex(), 0);
280     setValue(index.getCountIndex(), 0);
281     setValue(index.getVarianceIndex(), 0);
282   }
283 
284   /**
285    * Calculate the variance resulting from adding the sample value
286    * <code>newValue</code> to the a population with original attributes (
287    * <code>sum</code>, <code>count</code>, <code>variance</code>).
288    *
289    * @param sum Original total of sample values.
290    * @param count Original number of sample values.
291    * @param variance Original sample variance.
292    * @param newValue New value.
293    * @return New sample variance.
294    */
295   private double calculateVariance(double sum,
296                                    long count,
297                                    double variance,
298                                    double newValue) {
299     if (count == 0) {
300       return 0;
301     }
302 
303     final long t1 = count + 1;
304     final double t2 = newValue - sum / count;
305 
306     return
307       (count * variance) / (count + 1) +
308       (count / (double)(t1 * t1)) * t2 * t2;
309   }
310 
311   /**
312    * Calculate the variance resulting from combining two sample populations.
313    *
314    * @param s1 Total of samples in first population.
315    * @param n1 Count of samples in first population.
316    * @param v1 Variance of samples in first population.
317    * @param s2 Total of samples in second population.
318    * @param n2 Count of samples in second population.
319    * @param v2 Variance of samples in second population.
320    * @return Variance of combined population.
321    */
322   private double calculateVariance(double s1, long n1, double v1,
323                                    double s2, long n2, double v2) {
324     if (n1 == 0) {
325       return v2;
326     }
327     else if (n2 == 0) {
328       return v1;
329     }
330 
331     final double s = s1 + s2;
332     final long n = n1 + n2;
333 
334     final double term1 = s1 / n1 - s / n;
335     final double term2 = s2 / n2 - s / n;
336 
337     return (n1 * (term1 * term1 + v1) + n2 * (term2 * term2 + v2)) / n;
338   }
339 
340   /**
341    * Get the total sample value for the sample statistic specified by
342    * <code>index</code>.
343    *
344    * @param index The index.
345    * @return The sum.
346    */
347   public long getSum(LongSampleIndex index) {
348     return getValue(index.getSumIndex());
349   }
350 
351   /**
352    * Get the total sample value for the sample statistic specified by
353    * <code>index</code>.
354    *
355    * @param index The index.
356    * @return The sum.
357    */
358   public double getSum(DoubleSampleIndex index) {
359     return getValue(index.getSumIndex());
360   }
361 
362   /**
363    * Get the number of samples for the sample statistic specified by
364    * <code>index</code>.
365    *
366    * @param index The index.
367    * @return The count.
368    */
369   public long getCount(SampleIndex index) {
370     return getValue(index.getCountIndex());
371   }
372 
373   /**
374    * Get the sample variance for the sample statistic specified by
375    * <code>index</code>.
376    *
377    * @param index The index.
378    * @return The count.
379    */
380   public double getVariance(SampleIndex index) {
381     return getValue(index.getVarianceIndex());
382   }
383 
384   /**
385    * Add the values of another <code>StatisticsSet</code> to ours. Assumes we
386    * don't need to synchronise access to operand.
387    *
388    * <p>
389    * <strong>Currently the implementation assumes that the argument is actually
390    * a <code>StatisticsSetImplementation</code>.</strong>
391    * </p>
392    *
393    * <p>
394    * Synchronised to ensure we don't lose information.
395    * </p>.
396    *
397    * @param operand
398    *          The statistics set value to add.
399    */
400   public synchronized void add(ImmutableStatisticsSet operand) {
401 
402     final StatisticsSetImplementation operandImplementation =
403       (StatisticsSetImplementation)operand;
404 
405     final boolean[] isVarianceIndex = new boolean[m_doubleData.length];
406 
407     for (LongSampleIndex index : m_statisticsIndexMap.getLongSampleIndicies()) {
408       final LongIndex sumIndex = index.getSumIndex();
409       final LongIndex countIndex = index.getCountIndex();
410       final DoubleIndex varianceIndex = index.getVarianceIndex();
411 
412       setValue(varianceIndex,
413         calculateVariance(getValue(sumIndex),
414                           getValue(countIndex),
415                           getValue(varianceIndex),
416                           operand.getValue(sumIndex),
417                           operand.getValue(countIndex),
418                           operand.getValue(varianceIndex)));
419 
420       isVarianceIndex[varianceIndex.getValue()] = true;
421     }
422 
423     for (DoubleSampleIndex index :
424          m_statisticsIndexMap.getDoubleSampleIndicies()) {
425 
426       final DoubleIndex sumIndex = index.getSumIndex();
427       final LongIndex countIndex = index.getCountIndex();
428       final DoubleIndex varianceIndex = index.getVarianceIndex();
429 
430       setValue(varianceIndex,
431                calculateVariance(getValue(sumIndex),
432                                  getValue(countIndex),
433                                  getValue(varianceIndex),
434                                  operand.getValue(sumIndex),
435                                  operand.getValue(countIndex),
436                                  operand.getValue(varianceIndex)));
437 
438       isVarianceIndex[varianceIndex.getValue()] = true;
439     }
440 
441     final long[] longData = operandImplementation.m_longData;
442 
443     for (int i = 0; i < longData.length; i++) {
444       m_longData[i] += longData[i];
445     }
446 
447     final double[] doubleData = operandImplementation.m_doubleData;
448 
449     for (int i = 0; i < doubleData.length; i++) {
450       if (!isVarianceIndex[i]) {
451         m_doubleData[i] += doubleData[i];
452       }
453     }
454 
455     m_zero = false;
456 
457     if (operand.isComposite()) {
458       setIsComposite();
459     }
460   }
461 
462   public synchronized boolean isZero() {
463     return m_zero;
464   }
465 
466   public synchronized boolean isComposite() {
467     return m_composite;
468   }
469 
470   public synchronized void setIsComposite() {
471     m_composite = true;
472   }
473 
474   /**
475    * Implement value based equality. Mainly used by unit tests.
476    *
477    * @param o <code>Object</code> to compare to.
478    * @return <code>true</code> if and only if the two objects are equal.
479    */
480   public synchronized boolean equals(Object o) {
481     if (o == this) {
482       return true;
483     }
484 
485     if (o == null || o.getClass() != StatisticsSetImplementation.class) {
486       return false;
487     }
488 
489     final StatisticsSetImplementation otherStatistics =
490       (StatisticsSetImplementation)o;
491 
492     synchronized (otherStatistics) {
493 
494       if (m_composite != otherStatistics.m_composite) {
495         return false;
496       }
497 
498       final long[] otherLongData = otherStatistics.m_longData;
499 
500       for (int i = 0; i < m_longData.length; i++) {
501         if (m_longData[i] != otherLongData[i]) {
502           return false;
503         }
504       }
505 
506       for (int i = 0; i < m_doubleData.length; i++) {
507         if (m_doubleData[i] != otherStatistics.m_doubleData[i]) {
508           return false;
509         }
510       }
511 
512       final long[] otherTransientLongData = otherStatistics.m_transientLongData;
513 
514       for (int i = 0; i < m_transientLongData.length; i++) {
515         if (m_transientLongData[i] != otherTransientLongData[i]) {
516           return false;
517         }
518       }
519     }
520 
521     return true;
522   }
523 
524   /**
525    * Defer to <code>Object.hashCode()</code>.
526    *
527    * <p>We define <code>hashCode</code> to keep Checkstyle happy, but
528    * we don't use it.
529    *
530    * @return The hash code.
531    */
532   public int hashCode() {
533     long result = 0;
534 
535     for (int i = 0; i < m_longData.length; i++) {
536       result ^= m_longData[i];
537     }
538 
539     for (int i = 0; i < m_doubleData.length; i++) {
540       result ^= Double.doubleToRawLongBits(m_doubleData[i]);
541     }
542 
543     for (int i = 0; i < m_transientLongData.length; i++) {
544       result ^= m_transientLongData[i];
545     }
546 
547     return (int)(result ^ (result >> 32));
548   }
549 
550   /**
551    * Return a <code>String</code> representation of this
552    * <code>StatisticsSet</code>.
553    *
554    * @return The <code>String</code>
555    */
556   public synchronized String toString() {
557     final StringBuilder result = new StringBuilder();
558 
559     result.append("StatisticsSet = {{");
560 
561     for (int i = 0; i < m_longData.length; i++) {
562       if (i != 0) {
563         result.append(", ");
564       }
565 
566       result.append(m_longData[i]);
567     }
568 
569     result.append("}, {");
570 
571     for (int i = 0; i < m_doubleData.length; i++) {
572       if (i != 0) {
573         result.append(", ");
574       }
575 
576       result.append(m_doubleData[i]);
577     }
578 
579     result.append("}, {");
580 
581     for (int i = 0; i < m_transientLongData.length; i++) {
582       if (i != 0) {
583         result.append(", ");
584       }
585 
586       result.append(m_transientLongData[i]);
587     }
588 
589     result.append("}, composite = ");
590     result.append(m_composite ? "true" : "false");
591     result.append("}");
592 
593     return result.toString();
594   }
595 
596   /**
597    * Efficient externalisation method used by {@link
598    * StatisticsSetFactory#writeStatisticsExternal}.
599    *
600    * <p>Why not implement java.io.Externalizable? Because I consider it
601    * morally dubious:
602    * <ul>
603    * <li>It requires a public default constructor which either has to bypass
604    * class constraints, or discover external dependencies through singletons or
605    * a service registry</li>
606    * <li><em>readExternal</em> should only be called by the serialisation
607    * engine, but needs to be public.</li>
608    * </ul>
609    * </p>
610    *
611    * <p>Synchronised to ensure a consistent view.</p>
612    *
613    * <p>If you change this, update TestStatisticsMap serialVersionUID.</p>
614    *
615    * @param out Handle to the output stream.
616    * @param serialiser <code>Serialiser</code> helper object.
617    * @exception IOException If an error occurs.
618    * @see #StatisticsSetImplementation(StatisticsIndexMap, ObjectInput,
619    * Serialiser)
620    */
621   synchronized void writeExternal(ObjectOutput out, Serialiser serialiser)
622     throws IOException {
623     for (int i = 0; i < m_longData.length; i++) {
624       serialiser.writeLong(out, m_longData[i]);
625     }
626 
627     for (int i = 0; i < m_doubleData.length; i++) {
628       serialiser.writeDouble(out, m_doubleData[i]);
629     }
630 
631     out.writeBoolean(m_composite);
632   }
633 
634   /**
635    * Efficient externalisation method used by {@link
636    * StatisticsSetFactory#readStatisticsExternal}.
637    *
638    * <p>If you change this, update TestStatisticsMap serialVersionUID.</p>
639    *
640    * @param statisticsIndexMap The {@link StatisticsIndexMap} to use.
641    * @param in Handle to the input stream.
642    * @param serialiser <code>Serialiser</code> helper object.
643    * @exception IOException If an error occurs.
644    * @see #writeExternal(ObjectOutput, Serialiser)
645    */
646   StatisticsSetImplementation(StatisticsIndexMap statisticsIndexMap,
647                               ObjectInput in, Serialiser serialiser)
648     throws IOException {
649     this(statisticsIndexMap);
650 
651     for (int i = 0; i < m_longData.length; i++) {
652       m_longData[i] = serialiser.readLong(in);
653       m_zero &= m_longData[i] == 0;
654     }
655 
656     for (int i = 0; i < m_doubleData.length; i++) {
657       m_doubleData[i] = serialiser.readDouble(in);
658       m_zero &= m_doubleData[i] == 0;
659     }
660 
661     m_composite = in.readBoolean();
662   }
663 }