org.apache.avro.ipc.stats.Histogram Maven / Gradle / Ivy
/**
* 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.avro.ipc.stats;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
/**
* Represents a histogram of values. This class uses a {@link Segmenter}
* to determine which bucket to place a given value into. Also stores the last
* MAX_HISTORY_SIZE entries which have been added to this histogram, in order.
*
* Note that Histogram, by itself, is not synchronized.
* @param Bucket type. Often String, since buckets are typically
* used for their toString() representation.
* @param Type of value
*/
class Histogram {
/**
* How many recent additions we should track.
*/
public static final int MAX_HISTORY_SIZE = 20;
private Segmenter segmenter;
private int[] counts;
protected int totalCount;
private LinkedList recentAdditions;
/**
* Interface to determine which bucket to place a value in.
*
* Segmenters should be immutable, so many histograms can re-use
* the same segmenter.
*/
interface Segmenter {
/** Number of buckets to use. */
int size();
/**
* Which bucket to place value in.
*
* @return Index of bucket for the value. At least 0 and less than size().
* @throws SegmenterException if value does not fit in a bucket.
*/
int segment(T value);
/**
* Returns an iterator of buckets. The order of iteration
* is consistent with the segment numbers.
*/
Iterator getBuckets();
/**
* Returns a List of bucket boundaries. Useful for printing
* segmenters.
*/
List getBoundaryLabels();
/**
* Returns the bucket labels as an array;
*/
List getBucketLabels();
}
public static class SegmenterException extends RuntimeException {
public SegmenterException(String s) {
super(s);
}
}
public static class TreeMapSegmenter>
implements Segmenter {
private TreeMap index = new TreeMap();
public TreeMapSegmenter(SortedSet leftEndpoints) {
if (leftEndpoints.isEmpty()) {
throw new IllegalArgumentException(
"Endpoints must not be empty: " + leftEndpoints);
}
int i = 0;
for (T t : leftEndpoints) {
index.put(t, i++);
}
}
public int segment(T value) {
Map.Entry e = index.floorEntry(value);
if (e == null) {
throw new SegmenterException("Could not find bucket for: " + value);
}
return e.getValue();
}
@Override
public int size() {
return index.size();
}
private String rangeAsString(T a, T b) {
return String.format("[%s,%s)", a, b == null ? "infinity" : b);
}
@Override
public ArrayList getBoundaryLabels() {
ArrayList outArray = new ArrayList(index.keySet().size());
for (T obj: index.keySet()) {
outArray.add(obj.toString());
}
return outArray;
}
@Override
public ArrayList getBucketLabels() {
ArrayList outArray = new ArrayList(index.keySet().size());
Iterator bucketsIt = this.getBuckets();
while (bucketsIt.hasNext()) {
outArray.add(bucketsIt.next());
}
return outArray;
}
@Override
public Iterator getBuckets() {
return new Iterator() {
Iterator it = index.keySet().iterator();
T cur = it.next(); // there's always at least one element
int pos = 0;
@Override
public boolean hasNext() {
return (pos < index.keySet().size());
}
@Override
public String next() {
pos = pos + 1;
T left = cur;
cur = it.hasNext() ? it.next() : null;
return rangeAsString(left, cur);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Creates a histogram using the specified segmenter.
*/
public Histogram(Segmenter segmenter) {
this.segmenter = segmenter;
this.counts = new int[segmenter.size()];
this.recentAdditions = new LinkedList();
}
/** Tallies a value in the histogram. */
public void add(T value) {
int i = segmenter.segment(value);
counts[i]++;
totalCount++;
if (this.recentAdditions.size() > Histogram.MAX_HISTORY_SIZE) {
this.recentAdditions.pollLast();
}
this.recentAdditions.push(value);
}
/**
* Returns the underlying bucket values.
*/
public int[] getHistogram() {
return counts;
}
/**
* Returns the underlying segmenter used for this histogram.
*/
public Segmenter getSegmenter() {
return this.segmenter;
}
/**
* Returns values recently added to this histogram. These are in reverse
* order (most recent first).
*/
public List getRecentAdditions() {
return this.recentAdditions;
}
/** Returns the total count of entries. */
public int getCount() {
return totalCount;
}
public String toString() {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Entry e : entries()) {
if (!first) {
sb.append(";");
} else {
first = false;
}
sb.append(e.bucket).append("=").append(e.count);
}
return sb.toString();
}
static class Entry {
public Entry(B bucket, int count) {
this.bucket = bucket;
this.count = count;
}
B bucket;
int count;
}
private class EntryIterator implements Iterable>, Iterator> {
int i = 0;
Iterator bucketNameIterator = segmenter.getBuckets();
@Override
public Iterator> iterator() {
return this;
}
@Override
public boolean hasNext() {
return i < segmenter.size();
}
@Override
public Entry next() {
return new Entry(bucketNameIterator.next(), counts[i++]);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public Iterable> entries() {
return new EntryIterator();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy