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

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