| /** |
| * 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(); |
| } |
| } |