001package io.prometheus.metrics.model.snapshots;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.Iterator;
007import java.util.List;
008import java.util.stream.Stream;
009
010/**
011 * Immutable representation of native histogram buckets.
012 * <p>
013 * The bucket index defines the boundaries of the bucket,
014 * depending on the histogram's {@link HistogramSnapshot.HistogramDataPointSnapshot#getNativeSchema() schema}.
015 * <pre>
016 *     base = 2^(2^-schema)
017 *     lower bound = base^(index - 1)
018 *     upper bound = base^index
019 * </pre>
020 */
021public class NativeHistogramBuckets implements Iterable<NativeHistogramBucket> {
022
023    public static final NativeHistogramBuckets EMPTY = new NativeHistogramBuckets(new int[]{}, new long[]{});
024    private final int[] bucketIndexes; // sorted
025    private final long[] counts;
026
027    private NativeHistogramBuckets(int[] bucketIndexes, long[] counts) {
028        this.bucketIndexes = bucketIndexes;
029        this.counts = counts;
030    }
031
032    /**
033     * To create a new {@link NativeHistogramBuckets} instance, you can either use one of the static {@code of(...)}
034     * methods, or use {@link NativeHistogramBuckets#builder()}.
035     * @param bucketIndexes see class javadoc of {@link NativeHistogramBuckets}. May be empty.
036     * @param counts must have the same length as bucketIndexes
037     */
038    public static NativeHistogramBuckets of(int[] bucketIndexes, long[] counts) {
039        int[] bucketIndexesCopy = Arrays.copyOf(bucketIndexes, bucketIndexes.length);
040        long[] countsCopy = Arrays.copyOf(counts, counts.length);
041        sortAndValidate(bucketIndexesCopy, countsCopy);
042        return new NativeHistogramBuckets(bucketIndexesCopy, countsCopy);
043    }
044
045    /**
046     * To create a new {@link NativeHistogramBuckets} instance, you can either use one of the static {@code of(...)}
047     * methods, or use {@link NativeHistogramBuckets#builder()}.
048     * @param bucketIndexes see class javadoc of {@link NativeHistogramBuckets}. May be empty.
049     * @param counts must have the same size as bucketIndexes
050     */
051    public static NativeHistogramBuckets of(List<Integer> bucketIndexes, List<Long> counts) {
052        int[] bucketIndexesCopy = new int[bucketIndexes.size()];
053        for (int i=0; i<bucketIndexes.size(); i++) {
054            bucketIndexesCopy[i] = bucketIndexes.get(i);
055        }
056        long[] countsCopy = new long[counts.size()];
057        for (int i=0; i<counts.size(); i++) {
058            countsCopy[i] = counts.get(i);
059        }
060        sortAndValidate(bucketIndexesCopy, countsCopy);
061        return new NativeHistogramBuckets(bucketIndexesCopy, countsCopy);
062    }
063
064    public int size() {
065        return bucketIndexes.length;
066    }
067
068    private List<NativeHistogramBucket> asList() {
069        List<NativeHistogramBucket> result = new ArrayList<>(size());
070        for (int i=0; i<bucketIndexes.length; i++) {
071            result.add(new NativeHistogramBucket(bucketIndexes[i], counts[i]));
072        }
073        return Collections.unmodifiableList(result);
074    }
075
076    @Override
077    public Iterator<NativeHistogramBucket> iterator() {
078        return asList().iterator();
079    }
080
081    public Stream<NativeHistogramBucket> stream() {
082        return asList().stream();
083    }
084
085    public int getBucketIndex(int i) {
086        return bucketIndexes[i];
087    }
088
089    public long getCount(int i) {
090        return counts[i];
091    }
092
093    private static void sortAndValidate(int[] bucketIndexes, long[] counts) {
094        if (bucketIndexes.length != counts.length) {
095            throw new IllegalArgumentException("bucketIndexes.length == " + bucketIndexes.length + " but counts.length == " + counts.length + ". Expected the same length.");
096        }
097        sort(bucketIndexes, counts);
098        validate(bucketIndexes, counts);
099    }
100
101    private static void sort(int[] bucketIndexes, long[] counts) {
102        // Bubblesort. Should be efficient here as in most cases bucketIndexes is already sorted.
103        int n = bucketIndexes.length;
104        for (int i = 0; i < n - 1; i++) {
105            for (int j = 0; j < n - i - 1; j++) {
106                if (bucketIndexes[j] > bucketIndexes[j + 1]) {
107                    swap(j, j+1, bucketIndexes, counts);
108                }
109            }
110        }
111    }
112
113    private static void swap(int i, int j, int[] bucketIndexes, long[] counts) {
114        int tmpInt = bucketIndexes[j];
115        bucketIndexes[j] = bucketIndexes[i];
116        bucketIndexes[i] = tmpInt;
117        long tmpLong = counts[j];
118        counts[j] = counts[i];
119        counts[i] = tmpLong;
120    }
121
122    private static void validate(int[] bucketIndexes, long[] counts) {
123        // Preconditions:
124        // * bucketIndexes sorted
125        // * bucketIndexes and counts have the same length
126        for (int i=0; i<bucketIndexes.length; i++) {
127            if (counts[i] < 0) {
128                throw new IllegalArgumentException("Bucket counts cannot be negative.");
129            }
130            if (i > 0) {
131                if (bucketIndexes[i-1] == bucketIndexes[i]) {
132                    throw new IllegalArgumentException("Duplicate bucket index " + bucketIndexes[i]);
133                }
134            }
135        }
136    }
137
138    public static Builder builder() {
139        return new Builder();
140    }
141
142    public static class Builder {
143
144        private final List<Integer> bucketIndexes = new ArrayList<>();
145        private final List<Long> counts = new ArrayList<>();
146
147        private Builder() {}
148
149        /**
150         * Add a native histogram bucket. Call multiple times to add multiple buckets.
151         */
152        public Builder bucket(int bucketIndex, long count) {
153            bucketIndexes.add(bucketIndex);
154            counts.add(count);
155            return this;
156        }
157
158        public NativeHistogramBuckets build() {
159            return NativeHistogramBuckets.of(bucketIndexes, counts);
160        }
161    }
162}