blob: d9ff0c25c6a21c8a48078974113e53d6b4f9930c [file] [log] [blame]
/*
* 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.
*/
package org.apache.hadoop.fs.statistics;
import java.io.Serializable;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
/**
* A mean statistic represented as the sum and the sample count;
* the mean is calculated on demand.
* <p>
* It can be used to accrue values so as to dynamically update
* the mean. If so, know that there is no synchronization
* on the methods.
* </p>
* <p>
* If a statistic has 0 samples then it is considered to be empty.
* </p>
* <p>
* All 'empty' statistics are equivalent, independent of the sum value.
* </p>
* <p>
* For non-empty statistics, sum and sample values must match
* for equality.
* </p>
* <p>
* It is serializable and annotated for correct serializations with jackson2.
* </p>
* <p>
* Thread safety. The operations to add/copy sample data, are thread safe.
* </p>
* <ol>
* <li>{@link #add(MeanStatistic)}</li>
* <li>{@link #addSample(long)} </li>
* <li>{@link #clear()} </li>
* <li>{@link #setSamplesAndSum(long, long)}</li>
* <li>{@link #set(MeanStatistic)}</li>
* <li>{@link #setSamples(long)} and {@link #setSum(long)}</li>
* </ol>
* <p>
* So is the {@link #mean()} method. This ensures that when
* used to aggregated statistics, the aggregate value and sample
* count are set and evaluated consistently.
* </p>
* <p>
* Other methods marked as synchronized because Findbugs overreacts
* to the idea that some operations to update sum and sample count
* are synchronized, but that things like equals are not.
* </p>
*/
@InterfaceAudience.Public
@InterfaceStability.Evolving
public final class MeanStatistic implements Serializable, Cloneable {
private static final long serialVersionUID = 567888327998615425L;
/**
* Number of samples used to calculate
* the mean.
*/
private long samples;
/**
* sum of the values.
*/
private long sum;
/**
* Constructor, with some resilience against invalid sample counts.
* If the sample count is 0 or less, the sum is set to 0 and
* the sample count to 0.
* @param samples sample count.
* @param sum sum value
*/
public MeanStatistic(final long samples, final long sum) {
if (samples > 0) {
this.sum = sum;
this.samples = samples;
}
}
/**
* Create from another statistic.
* @param that source
*/
public MeanStatistic(MeanStatistic that) {
synchronized (that) {
set(that);
}
}
/**
* Create an empty statistic.
*/
public MeanStatistic() {
}
/**
* Get the sum of samples.
* @return the sum
*/
public synchronized long getSum() {
return sum;
}
/**
* Get the sample count.
* @return the sample count; 0 means empty
*/
public synchronized long getSamples() {
return samples;
}
/**
* Is a statistic empty?
* @return true if the sample count is 0
*/
@JsonIgnore
public synchronized boolean isEmpty() {
return samples == 0;
}
/**
* Set the values to 0.
*/
public void clear() {
setSamplesAndSum(0, 0);
}
/**
* Set the sum and samples.
* Synchronized.
* @param sampleCount new sample count.
* @param newSum new sum
*/
public synchronized void setSamplesAndSum(long sampleCount,
long newSum) {
setSamples(sampleCount);
setSum(newSum);
}
/**
* Set the statistic to the values of another.
* Synchronized.
* @param other the source.
*/
public void set(final MeanStatistic other) {
setSamplesAndSum(other.getSamples(), other.getSum());
}
/**
* Set the sum.
* @param sum new sum
*/
public synchronized void setSum(final long sum) {
this.sum = sum;
}
/**
* Set the sample count.
*
* If this is less than zero, it is set to zero.
* This stops an ill-formed JSON entry from
* breaking deserialization, or get an invalid sample count
* into an entry.
* @param samples sample count.
*/
public synchronized void setSamples(final long samples) {
if (samples < 0) {
this.samples = 0;
} else {
this.samples = samples;
}
}
/**
* Get the arithmetic mean value.
* @return the mean
*/
public synchronized double mean() {
return samples > 0
? ((double) sum) / samples
: 0.0d;
}
/**
* Add another MeanStatistic.
* @param other other value
*/
public synchronized MeanStatistic add(final MeanStatistic other) {
if (other.isEmpty()) {
return this;
}
long otherSamples;
long otherSum;
synchronized (other) {
otherSamples = other.samples;
otherSum = other.sum;
}
if (isEmpty()) {
samples = otherSamples;
sum = otherSum;
return this;
}
samples += otherSamples;
sum += otherSum;
return this;
}
/**
* Add a sample.
* Thread safe.
* @param value value to add to the sum
*/
public synchronized void addSample(long value) {
samples++;
sum += value;
}
/**
* The hash code is derived from the mean
* and sample count: if either is changed
* the statistic cannot be used as a key
* for hash tables/maps.
* @return a hash value
*/
@Override
public synchronized int hashCode() {
return Objects.hash(sum, samples);
}
@Override
public synchronized boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MeanStatistic that = (MeanStatistic) o;
if (isEmpty()) {
// if we are empty, then so must the other.
return that.isEmpty();
}
return getSum() == that.getSum() &&
getSamples() == that.getSamples();
}
@Override
public MeanStatistic clone() {
return copy();
}
/**
* Create a copy of this instance.
* @return copy.
*
*/
public MeanStatistic copy() {
return new MeanStatistic(this);
}
@Override
public String toString() {
return String.format("(samples=%d, sum=%d, mean=%.4f)",
samples, sum, mean());
}
}