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}