blob: 6fef833d78cb25265369d524761df04d225b1817 [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.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 <B> Bucket type. Often String, since buckets are typically
* used for their toString() representation.
* @param <T> Type of value
*/
class Histogram<B, T> {
/**
* How many recent additions we should track.
*/
public static final int MAX_HISTORY_SIZE = 20;
private Segmenter<B, T> segmenter;
private int[] counts;
protected int totalCount;
private LinkedList<T> 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<B, T> {
/** 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<B> getBuckets();
/**
* Returns a List of bucket boundaries. Useful for printing
* segmenters.
*/
List<String> getBoundaryLabels();
/**
* Returns the bucket labels as an array;
*/
List<String> getBucketLabels();
}
public static class SegmenterException extends RuntimeException {
public SegmenterException(String s) {
super(s);
}
}
public static class TreeMapSegmenter<T extends Comparable<T>>
implements Segmenter<String, T> {
private TreeMap<T, Integer> index = new TreeMap<T, Integer>();
public TreeMapSegmenter(SortedSet<T> 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<T, Integer> 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<String> getBoundaryLabels() {
ArrayList<String> outArray = new ArrayList<String>(index.keySet().size());
for (T obj: index.keySet()) {
outArray.add(obj.toString());
}
return outArray;
}
@Override
public ArrayList<String> getBucketLabels() {
ArrayList<String> outArray = new ArrayList<String>(index.keySet().size());
Iterator<String> bucketsIt = this.getBuckets();
while (bucketsIt.hasNext()) {
outArray.add(bucketsIt.next());
}
return outArray;
}
@Override
public Iterator<String> getBuckets() {
return new Iterator<String>() {
Iterator<T> 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<B, T> segmenter) {
this.segmenter = segmenter;
this.counts = new int[segmenter.size()];
this.recentAdditions = new LinkedList<T>();
}
/** 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<B, T> getSegmenter() {
return this.segmenter;
}
/**
* Returns values recently added to this histogram. These are in reverse
* order (most recent first).
*/
public List<T> 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<B> e : entries()) {
if (!first) {
sb.append(";");
} else {
first = false;
}
sb.append(e.bucket).append("=").append(e.count);
}
return sb.toString();
}
static class Entry<B> {
public Entry(B bucket, int count) {
this.bucket = bucket;
this.count = count;
}
B bucket;
int count;
}
private class EntryIterator implements Iterable<Entry<B>>, Iterator<Entry<B>> {
int i = 0;
Iterator<B> bucketNameIterator = segmenter.getBuckets();
@Override
public Iterator<Entry<B>> iterator() {
return this;
}
@Override
public boolean hasNext() {
return i < segmenter.size();
}
@Override
public Entry<B> next() {
return new Entry<B>(bucketNameIterator.next(), counts[i++]);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public Iterable<Entry<B>> entries() {
return new EntryIterator();
}
}