All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.grinder.statistics.StatisticsSetImplementation Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2000 - 2011 Philip Aston
// All rights reserved.
//
// This file is part of The Grinder software distribution. Refer to
// the file LICENSE which is part of The Grinder distribution for
// licensing details. The Grinder distribution is available on the
// Internet at http://grinder.sourceforge.net/
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package net.grinder.statistics;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;

import net.grinder.statistics.StatisticsIndexMap.DoubleIndex;
import net.grinder.statistics.StatisticsIndexMap.DoubleSampleIndex;
import net.grinder.statistics.StatisticsIndexMap.LongIndex;
import net.grinder.statistics.StatisticsIndexMap.LongSampleIndex;
import net.grinder.statistics.StatisticsIndexMap.SampleIndex;
import net.grinder.util.Serialiser;


/**
 * Store an array of raw statistics as unsigned long or double values. Clients
 * can access individual values using an index obtained from a {@link
 * StatisticsIndexMap}.
 *
 * @author Philip Aston
 */
final class StatisticsSetImplementation implements StatisticsSet {

  private final StatisticsIndexMap m_statisticsIndexMap;
  private final long[] m_longData;
  private final double[] m_doubleData;

  // Transient fields are context specific. They are not serialised, nor are
  // they added to other statistics sets. E.g. the "period" field.
  private transient long[] m_transientLongData;

  // true => all statistics are zero; false => they might be.
  private boolean m_zero = true;

  private boolean m_composite;

  /**
   * Creates a new StatisticsSetImplementation instance.
   *
   * @param statisticsIndexMap The {@link StatisticsIndexMap} to use.
   */
  StatisticsSetImplementation(StatisticsIndexMap statisticsIndexMap) {
    m_statisticsIndexMap = statisticsIndexMap;
    m_longData = new long[m_statisticsIndexMap.getNumberOfLongs()];
    m_doubleData = new double[m_statisticsIndexMap.getNumberOfDoubles()];
    m_transientLongData =
      new long[m_statisticsIndexMap.getNumberOfTransientLongs()];
  }

  /**
   * Reset this StatisticsSet to default values. Allows instance to
   * be reused.
   */
  public synchronized void reset() {
    if (!m_zero) {
      Arrays.fill(m_longData, 0);
      Arrays.fill(m_doubleData, 0);
      Arrays.fill(m_transientLongData, 0);
      m_zero = true;
      m_composite = false;
    }
  }

  /**
   * Clone this object.
   *
   * We don't use {@link Object#clone} as that's such a hog's arse of a
   * mechanism. It prevents us from using final variables, requires a lot of
   * casting, and that we catch CloneNotSupported exceptions - even if we know
   * they won't be thrown.
   *
   * @return A copy of this StatisticsSetImplementation.
   */
  public synchronized StatisticsSet snapshot() {
    final StatisticsSetImplementation result =
      new StatisticsSetImplementation(m_statisticsIndexMap);

    if (!m_zero) {
      synchronized (result) {
        System.arraycopy(m_longData,
                         0,
                         result.m_longData,
                         0,
                         result.m_longData.length);

        System.arraycopy(m_doubleData,
                         0,
                         result.m_doubleData,
                         0,
                         result.m_doubleData.length);

        System.arraycopy(m_transientLongData,
                         0,
                         result.m_transientLongData,
                         0, result.m_transientLongData.length);

        result.m_zero = false;
        result.m_composite = m_composite;
      }
    }

    return result;
  }

  /**
   * Return the value specified by index.
   *
   * 

Unfortunately the Java language specification does not make * long access atomic, so we must synchronise.

* * @param index The process specific index. * @return The value. */ public synchronized long getValue(LongIndex index) { if (index.isTransient()) { return m_transientLongData[index.getValue()]; } return m_longData[index.getValue()]; } /** * Return the value specified by index. * *

Unfortunately the Java language specification does not make * double access atomic, so we must synchronise.

* * @param index The process specific index. * @return The value. */ public synchronized double getValue(DoubleIndex index) { return m_doubleData[index.getValue()]; } /** * Set the value specified by index. * *

Unfortunately the Java language specification does not make * long access atomic, so we must synchronise.

* * @param index The process specific index. * @param value The value. */ public synchronized void setValue(LongIndex index, long value) { if (index.isTransient()) { m_transientLongData[index.getValue()] = value; } else { m_longData[index.getValue()] = value; } m_zero &= value == 0; } /** * Set the value specified by index. * *

Unfortunately the Java language specification does not make * double access atomic, so we must synchronise.

* * @param index The process specific index. * @param value The value. */ public synchronized void setValue(DoubleIndex index, double value) { m_doubleData[index.getValue()] = value; m_zero &= value == 0; } /** * Add value to the value specified by * index. * *

Synchronised to ensure we don't lose information.

. * * @param index The process specific index. * @param value The value. */ public synchronized void addValue(LongIndex index, long value) { if (!index.isTransient()) { m_longData[index.getValue()] += value; m_zero &= value == 0; } } /** * Add value to the value specified by * index. * *

Synchronised to ensure we don't lose information.

. * * @param index The process specific index. * @param value The value. */ public synchronized void addValue(DoubleIndex index, double value) { m_doubleData[index.getValue()] += value; m_zero &= value == 0; } /** * Add sample value to the sample statistic specified by * index. * * @param index The index. * @param value The value. */ public synchronized void addSample(LongSampleIndex index, long value) { setValue(index.getVarianceIndex(), calculateVariance(getValue(index.getSumIndex()), getValue(index.getCountIndex()), getValue(index.getVarianceIndex()), value)); m_longData[index.getSumIndex().getValue()] += value; ++m_longData[index.getCountIndex().getValue()]; m_zero = false; } /** * Add sample value to the sample statistic specified by * index. * * @param index The index. * @param value The value. */ public synchronized void addSample(DoubleSampleIndex index, double value) { setValue(index.getVarianceIndex(), calculateVariance(getValue(index.getSumIndex()), getValue(index.getCountIndex()), getValue(index.getVarianceIndex()), value)); m_doubleData[index.getSumIndex().getValue()] += value; ++m_longData[index.getCountIndex().getValue()]; m_zero = false; } /** * Reset the sample statistic specified by index. * * @param index */ public synchronized void reset(LongSampleIndex index) { setValue(index.getSumIndex(), 0); setValue(index.getCountIndex(), 0); setValue(index.getVarianceIndex(), 0); } /** * Reset the sample statistic specified by index. * * @param index */ public synchronized void reset(DoubleSampleIndex index) { setValue(index.getSumIndex(), 0); setValue(index.getCountIndex(), 0); setValue(index.getVarianceIndex(), 0); } /** * Calculate the variance resulting from adding the sample value * newValue to the a population with original attributes ( * sum, count, variance). * * @param sum Original total of sample values. * @param count Original number of sample values. * @param variance Original sample variance. * @param newValue New value. * @return New sample variance. */ private double calculateVariance(double sum, long count, double variance, double newValue) { if (count == 0) { return 0; } final long t1 = count + 1; final double t2 = newValue - sum / count; return (count * variance) / (count + 1) + (count / (double)(t1 * t1)) * t2 * t2; } /** * Calculate the variance resulting from combining two sample populations. * * @param s1 Total of samples in first population. * @param n1 Count of samples in first population. * @param v1 Variance of samples in first population. * @param s2 Total of samples in second population. * @param n2 Count of samples in second population. * @param v2 Variance of samples in second population. * @return Variance of combined population. */ private double calculateVariance(double s1, long n1, double v1, double s2, long n2, double v2) { if (n1 == 0) { return v2; } else if (n2 == 0) { return v1; } final double s = s1 + s2; final long n = n1 + n2; final double term1 = s1 / n1 - s / n; final double term2 = s2 / n2 - s / n; return (n1 * (term1 * term1 + v1) + n2 * (term2 * term2 + v2)) / n; } /** * Get the total sample value for the sample statistic specified by * index. * * @param index The index. * @return The sum. */ public long getSum(LongSampleIndex index) { return getValue(index.getSumIndex()); } /** * Get the total sample value for the sample statistic specified by * index. * * @param index The index. * @return The sum. */ public double getSum(DoubleSampleIndex index) { return getValue(index.getSumIndex()); } /** * Get the number of samples for the sample statistic specified by * index. * * @param index The index. * @return The count. */ public long getCount(SampleIndex index) { return getValue(index.getCountIndex()); } /** * Get the sample variance for the sample statistic specified by * index. * * @param index The index. * @return The count. */ public double getVariance(SampleIndex index) { return getValue(index.getVarianceIndex()); } /** * Add the values of another StatisticsSet to ours. Assumes we * don't need to synchronise access to operand. * *

* Currently the implementation assumes that the argument is actually * a StatisticsSetImplementation. *

* *

* Synchronised to ensure we don't lose information. *

. * * @param operand * The statistics set value to add. */ public synchronized void add(ImmutableStatisticsSet operand) { final StatisticsSetImplementation operandImplementation = (StatisticsSetImplementation)operand; final boolean[] isVarianceIndex = new boolean[m_doubleData.length]; for (LongSampleIndex index : m_statisticsIndexMap.getLongSampleIndicies()) { final LongIndex sumIndex = index.getSumIndex(); final LongIndex countIndex = index.getCountIndex(); final DoubleIndex varianceIndex = index.getVarianceIndex(); setValue(varianceIndex, calculateVariance(getValue(sumIndex), getValue(countIndex), getValue(varianceIndex), operand.getValue(sumIndex), operand.getValue(countIndex), operand.getValue(varianceIndex))); isVarianceIndex[varianceIndex.getValue()] = true; } for (DoubleSampleIndex index : m_statisticsIndexMap.getDoubleSampleIndicies()) { final DoubleIndex sumIndex = index.getSumIndex(); final LongIndex countIndex = index.getCountIndex(); final DoubleIndex varianceIndex = index.getVarianceIndex(); setValue(varianceIndex, calculateVariance(getValue(sumIndex), getValue(countIndex), getValue(varianceIndex), operand.getValue(sumIndex), operand.getValue(countIndex), operand.getValue(varianceIndex))); isVarianceIndex[varianceIndex.getValue()] = true; } final long[] longData = operandImplementation.m_longData; for (int i = 0; i < longData.length; i++) { m_longData[i] += longData[i]; } final double[] doubleData = operandImplementation.m_doubleData; for (int i = 0; i < doubleData.length; i++) { if (!isVarianceIndex[i]) { m_doubleData[i] += doubleData[i]; } } m_zero = false; if (operand.isComposite()) { setIsComposite(); } } public synchronized boolean isZero() { return m_zero; } public synchronized boolean isComposite() { return m_composite; } public synchronized void setIsComposite() { m_composite = true; } /** * Implement value based equality. Mainly used by unit tests. * * @param o Object to compare to. * @return true if and only if the two objects are equal. */ public synchronized boolean equals(Object o) { if (o == this) { return true; } if (o == null || o.getClass() != StatisticsSetImplementation.class) { return false; } final StatisticsSetImplementation otherStatistics = (StatisticsSetImplementation)o; synchronized (otherStatistics) { if (m_composite != otherStatistics.m_composite) { return false; } final long[] otherLongData = otherStatistics.m_longData; for (int i = 0; i < m_longData.length; i++) { if (m_longData[i] != otherLongData[i]) { return false; } } for (int i = 0; i < m_doubleData.length; i++) { if (m_doubleData[i] != otherStatistics.m_doubleData[i]) { return false; } } final long[] otherTransientLongData = otherStatistics.m_transientLongData; for (int i = 0; i < m_transientLongData.length; i++) { if (m_transientLongData[i] != otherTransientLongData[i]) { return false; } } } return true; } /** * Defer to Object.hashCode(). * *

We define hashCode to keep Checkstyle happy, but * we don't use it. * * @return The hash code. */ public int hashCode() { long result = 0; for (int i = 0; i < m_longData.length; i++) { result ^= m_longData[i]; } for (int i = 0; i < m_doubleData.length; i++) { result ^= Double.doubleToRawLongBits(m_doubleData[i]); } for (int i = 0; i < m_transientLongData.length; i++) { result ^= m_transientLongData[i]; } return (int)(result ^ (result >> 32)); } /** * Return a String representation of this * StatisticsSet. * * @return The String */ public synchronized String toString() { final StringBuilder result = new StringBuilder(); result.append("StatisticsSet = {{"); for (int i = 0; i < m_longData.length; i++) { if (i != 0) { result.append(", "); } result.append(m_longData[i]); } result.append("}, {"); for (int i = 0; i < m_doubleData.length; i++) { if (i != 0) { result.append(", "); } result.append(m_doubleData[i]); } result.append("}, {"); for (int i = 0; i < m_transientLongData.length; i++) { if (i != 0) { result.append(", "); } result.append(m_transientLongData[i]); } result.append("}, composite = "); result.append(m_composite ? "true" : "false"); result.append("}"); return result.toString(); } /** * Efficient externalisation method used by {@link * StatisticsSetFactory#writeStatisticsExternal}. * *

Why not implement java.io.Externalizable? Because I consider it * morally dubious: *

    *
  • It requires a public default constructor which either has to bypass * class constraints, or discover external dependencies through singletons or * a service registry
  • *
  • readExternal should only be called by the serialisation * engine, but needs to be public.
  • *
*

* *

Synchronised to ensure a consistent view.

* *

If you change this, update TestStatisticsMap serialVersionUID.

* * @param out Handle to the output stream. * @param serialiser Serialiser helper object. * @exception IOException If an error occurs. * @see #StatisticsSetImplementation(StatisticsIndexMap, ObjectInput, * Serialiser) */ synchronized void writeExternal(ObjectOutput out, Serialiser serialiser) throws IOException { for (int i = 0; i < m_longData.length; i++) { serialiser.writeLong(out, m_longData[i]); } for (int i = 0; i < m_doubleData.length; i++) { serialiser.writeDouble(out, m_doubleData[i]); } out.writeBoolean(m_composite); } /** * Efficient externalisation method used by {@link * StatisticsSetFactory#readStatisticsExternal}. * *

If you change this, update TestStatisticsMap serialVersionUID.

* * @param statisticsIndexMap The {@link StatisticsIndexMap} to use. * @param in Handle to the input stream. * @param serialiser Serialiser helper object. * @exception IOException If an error occurs. * @see #writeExternal(ObjectOutput, Serialiser) */ StatisticsSetImplementation(StatisticsIndexMap statisticsIndexMap, ObjectInput in, Serialiser serialiser) throws IOException { this(statisticsIndexMap); for (int i = 0; i < m_longData.length; i++) { m_longData[i] = serialiser.readLong(in); m_zero &= m_longData[i] == 0; } for (int i = 0; i < m_doubleData.length; i++) { m_doubleData[i] = serialiser.readDouble(in); m_zero &= m_doubleData[i] == 0; } m_composite = in.readBoolean(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy