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

com.ringcentral.platform.metrics.histogram.Histogram Maven / Gradle / Ivy

The newest version!
package com.ringcentral.platform.metrics.histogram;

import com.ringcentral.platform.metrics.Meter;
import com.ringcentral.platform.metrics.labels.LabelValues;
import com.ringcentral.platform.metrics.measurables.MeasurableType;
import com.ringcentral.platform.metrics.scale.Scale;
import com.ringcentral.platform.metrics.scale.ScaleBuilder;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.ringcentral.platform.metrics.labels.LabelValues.NO_LABEL_VALUES;
import static com.ringcentral.platform.metrics.measurables.MeasurableType.*;
import static com.ringcentral.platform.metrics.scale.ExpScaleBuilder.expScale;
import static com.ringcentral.platform.metrics.scale.LinearScaleBuilder.linearScale;
import static com.ringcentral.platform.metrics.utils.Preconditions.checkArgument;
import static com.ringcentral.platform.metrics.utils.TimeUnitUtils.convertTimeUnit;
import static java.lang.Math.*;
import static java.util.concurrent.TimeUnit.*;
import static java.util.stream.Collectors.toSet;

public interface Histogram extends Meter {
    class TotalSum implements HistogramMeasurable {

        static final int HASH_CODE = "Histogram.TotalSum".hashCode();

        @Override
        public MeasurableType type() {
            return LONG;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || (other != null && getClass() == other.getClass());
        }

        @Override
        public int hashCode() {
            return HASH_CODE;
        }
    }

    TotalSum TOTAL_SUM = new TotalSum();

    class Min implements HistogramMeasurable {

        static final int HASH_CODE = "Histogram.Min".hashCode();

        @Override
        public MeasurableType type() {
            return LONG;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || (other != null && getClass() == other.getClass());
        }

        @Override
        public int hashCode() {
            return HASH_CODE;
        }
    }

    Min MIN = new Min();

    class Max implements HistogramMeasurable {

        static final int HASH_CODE = "Histogram.Max".hashCode();

        @Override
        public MeasurableType type() {
            return LONG;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || (other != null && getClass() == other.getClass());
        }

        @Override
        public int hashCode() {
            return HASH_CODE;
        }
    }

    Max MAX = new Max();

    class Mean implements HistogramMeasurable {

        static final int HASH_CODE = "Histogram.Mean".hashCode();

        @Override
        public MeasurableType type() {
            return DOUBLE;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || (other != null && getClass() == other.getClass());
        }

        @Override
        public int hashCode() {
            return HASH_CODE;
        }
    }

    Mean MEAN = new Mean();

    class StandardDeviation implements HistogramMeasurable {

        static final int HASH_CODE = "Histogram.StandardDeviation".hashCode();

        @Override
        public MeasurableType type() {
            return DOUBLE;
        }

        @Override
        public boolean equals(Object other) {
            return this == other || (other != null && getClass() == other.getClass());
        }

        @Override
        public int hashCode() {
            return HASH_CODE;
        }
    }

    StandardDeviation STANDARD_DEVIATION = new StandardDeviation();

    class Percentile implements HistogramMeasurable, Comparable {

        private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100.0);

        private final double quantile;
        private final String quantileAsString;
        private final String quantileDecimalPartAsString;
        private final double percentile;
        private final int hashCode;

        public Percentile(double quantile) {
            checkArgument(
                !(quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)),
                quantile + " is not in [0.0 .. 1.0]");

            this.quantile = quantile;
            this.quantileAsString = Double.toString(quantile);
            String afterPoint = this.quantileAsString.substring(this.quantileAsString.indexOf(".") + 1);
            this.quantileDecimalPartAsString = afterPoint.length() > 1 ? afterPoint : afterPoint + "0";
            this.percentile = min(max(BigDecimal.valueOf(quantile).multiply(BIG_DECIMAL_100).doubleValue(), 0.0), 100.0);
            this.hashCode = Objects.hash("Histogram.Percentile", quantile);
        }

        public static Percentile of(double quantile) {
            return new Percentile(quantile);
        }

        @Override
        public MeasurableType type() {
            return DOUBLE;
        }

        public double quantile() {
            return quantile;
        }

        public String quantileAsString() {
            return quantileAsString;
        }

        public String quantileDecimalPartAsString() {
            return quantileDecimalPartAsString;
        }

        public double percentile() {
            return percentile;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }

            if (other == null || getClass() != other.getClass()) {
                return false;
            }

            Percentile that = (Percentile)other;

            if (hashCode != that.hashCode) {
                return false;
            }

            return quantile == that.quantile;
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

        @Override
        public int compareTo(Percentile right) {
            return Double.compare(quantile, right.quantile);
        }
    }

    Percentile PERCENTILE_1 = Percentile.of(0.01);
    Percentile PERCENTILE_5 = Percentile.of(0.05);
    Percentile PERCENTILE_10 = Percentile.of(0.1);
    Percentile PERCENTILE_15 = Percentile.of(0.15);
    Percentile PERCENTILE_20 = Percentile.of(0.2);
    Percentile PERCENTILE_25 = Percentile.of(0.25);
    Percentile PERCENTILE_30 = Percentile.of(0.3);
    Percentile PERCENTILE_35 = Percentile.of(0.35);
    Percentile PERCENTILE_40 = Percentile.of(0.4);
    Percentile PERCENTILE_45 = Percentile.of(0.45);
    Percentile PERCENTILE_50 = Percentile.of(0.5);
    Percentile PERCENTILE_55 = Percentile.of(0.55);
    Percentile PERCENTILE_60 = Percentile.of(0.6);
    Percentile PERCENTILE_65 = Percentile.of(0.65);
    Percentile PERCENTILE_70 = Percentile.of(0.7);
    Percentile PERCENTILE_75 = Percentile.of(0.75);
    Percentile PERCENTILE_80 = Percentile.of(0.8);
    Percentile PERCENTILE_85 = Percentile.of(0.85);
    Percentile PERCENTILE_90 = Percentile.of(0.9);
    Percentile PERCENTILE_95 = Percentile.of(0.95);
    Percentile PERCENTILE_99 = Percentile.of(0.99);
    Percentile PERCENTILE_999 = Percentile.of(0.999);

    class Bucket implements HistogramMeasurable, Comparable {

        private final double upperBoundInUnits;
        private final TimeUnit upperBoundUnit;
        private final double upperBound;
        private final long upperBoundAsLong;
        private final boolean inf;
        private final boolean negativeInf;
        private final String upperBoundAsString;
        private final String upperBoundAsStringWithUnit;
        private final Map unitToUpperBoundAsStringWithUnit = new EnumMap<>(TimeUnit.class);
        private final String upperBoundAsNumberString;
        private final String upperBoundSecAsNumberString;
        private final int hashCode;

        public Bucket(double upperBound) {
            this(upperBound, null);
        }

        public Bucket(double upperBoundInUnits, TimeUnit upperBoundUnit) {
            this.upperBoundInUnits = upperBoundInUnits;
            this.upperBoundUnit = upperBoundUnit;

            this.upperBound =
                Double.isInfinite(upperBoundInUnits) || upperBoundUnit == null || upperBoundUnit == NANOSECONDS ?
                upperBoundInUnits :
                upperBoundUnit.toNanos(1L) * upperBoundInUnits;

            if (Double.isNaN(this.upperBound)) {
                throw new IllegalArgumentException(
                    upperBoundInUnits
                    + (upperBoundUnit != null ? " " + upperBoundUnit.name().toLowerCase(Locale.ENGLISH) : "")
                    + " in nanos results in an overflow");
            }

            this.upperBoundAsLong = round(this.upperBound);
            this.inf = (this.upperBoundAsLong == Long.MAX_VALUE);
            this.negativeInf = (this.upperBoundAsLong == Long.MIN_VALUE);
            this.upperBoundAsString = upperBoundAsString(this.upperBound);
            TimeUnit resolvedUpperBoundUnit = upperBoundUnit != null ? upperBoundUnit : NANOSECONDS;

            if (Double.isInfinite(upperBoundInUnits)) {
                this.upperBoundAsStringWithUnit = upperBoundAsString(upperBoundInUnits);

                for (TimeUnit unit : TimeUnit.values()) {
                    unitToUpperBoundAsStringWithUnit.put(unit, this.upperBoundAsStringWithUnit);
                }
            } else {
                for (TimeUnit unit : TimeUnit.values()) {
                    String unitAsString;

                    if (unit == NANOSECONDS) {
                        unitAsString = "ns";
                    } else if (unit == MICROSECONDS) {
                        unitAsString = "us";
                    } else if (unit == MILLISECONDS) {
                        unitAsString = "ms";
                    } else if (unit == SECONDS) {
                        unitAsString = "sec";
                    } else if (unit == HOURS) {
                        unitAsString = "h";
                    } else if (unit == DAYS) {
                        unitAsString = "d";
                    } else  {
                        unitAsString = unit.name().toLowerCase(Locale.ENGLISH);
                    }

                    double convertedUpperBoundInUnits;

                    if (unit == resolvedUpperBoundUnit) {
                        convertedUpperBoundInUnits = upperBoundInUnits;
                    } else if (unit == MILLISECONDS) {
                        convertedUpperBoundInUnits = BigDecimal.valueOf(upperBoundInUnits).multiply(
                            BigDecimal.valueOf(resolvedUpperBoundUnit.toNanos(1L)).multiply(BigDecimal.valueOf(0.000001))).doubleValue();
                    } else if (unit == SECONDS) {
                        convertedUpperBoundInUnits = BigDecimal.valueOf(upperBoundInUnits).multiply(
                            BigDecimal.valueOf(resolvedUpperBoundUnit.toNanos(1L)).multiply(BigDecimal.valueOf(0.000000001))).doubleValue();
                    } else {
                        convertedUpperBoundInUnits = convertTimeUnit(upperBoundInUnits, resolvedUpperBoundUnit, unit);
                    }

                    unitToUpperBoundAsStringWithUnit.put(
                        unit,
                        upperBoundAsString(convertedUpperBoundInUnits) + unitAsString);
                }

                this.upperBoundAsStringWithUnit = unitToUpperBoundAsStringWithUnit.get(resolvedUpperBoundUnit);
            }

            this.upperBoundAsNumberString = upperBoundAsNumberString(this.upperBound);

            this.upperBoundSecAsNumberString = withoutTrailingZeros(String.valueOf(
                Double.isInfinite(upperBoundInUnits) || upperBoundUnit == SECONDS ?
                upperBoundInUnits :
                // instead of upperBoundInUnits * ((1.0 * (upperBoundUnit != null ? upperBoundUnit : NANOSECONDS).toNanos(1L)) / NANOS_PER_SEC)
                // for example, 1.7000000000000002 -> 1.7
                BigDecimal.valueOf(upperBoundInUnits).multiply(
                    BigDecimal.valueOf(resolvedUpperBoundUnit.toNanos(1L)).multiply(BigDecimal.valueOf(0.000000001))).doubleValue()));

            this.hashCode = Objects.hash("Histogram.Bucket", upperBoundInUnits, resolvedUpperBoundUnit);
        }

        static String upperBoundAsString(double b) {
            if (b == Double.POSITIVE_INFINITY) {
                return "inf";
            }

            if (b == Double.NEGATIVE_INFINITY) {
                return "negativeInf";
            }

            return upperBoundAsNumberString(b).replace('.', 'p');
        }

        static String upperBoundAsNumberString(double b) {
            return withoutTrailingZeros(Double.toString(b));
        }

        static String withoutTrailingZeros(String s) {
            return s.replaceAll("\\.0+$", "");
        }

        public static Bucket of(double upperBound) {
            return new Bucket(upperBound);
        }

        public static Bucket of(double upperBoundInUnits, TimeUnit upperBoundUnit) {
            return new Bucket(upperBoundInUnits, upperBoundUnit);
        }

        @Override
        public MeasurableType type() {
            return LONG;
        }

        /**
         * @return inclusive upper bound in units
         */
        public double upperBoundInUnits() {
            return upperBoundInUnits;
        }

        public TimeUnit upperBoundUnit() {
            return upperBoundUnit;
        }

        public long upperBoundAsLong() {
            return upperBoundAsLong;
        }

        public boolean isInf() {
            return inf;
        }

        public boolean isNegativeInf() {
            return negativeInf;
        }

        public String upperBoundAsString() {
            return upperBoundAsString;
        }

        public String upperBoundAsStringWithUnit() {
            return upperBoundAsStringWithUnit;
        }

        public String upperBoundAsStringWithUnit(TimeUnit unit) {
            return unitToUpperBoundAsStringWithUnit.get(unit);
        }

        /**
         * @return the string representation of the upper bound that can converted to {@code Double} via {@code Double.valueOf()}.
         *         For example, Bucket.of(25.5).upperBoundAsNumberString().equals("25.5").
         *
         */
        public String upperBoundAsNumberString() {
            return upperBoundAsNumberString;
        }

        public String upperBoundSecAsNumberString() {
            return upperBoundSecAsNumberString;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }

            if (other == null || getClass() != other.getClass()) {
                return false;
            }

            Bucket that = (Bucket)other;

            if (hashCode != that.hashCode) {
                return false;
            }

            return upperBoundInUnits == that.upperBoundInUnits
                && upperBoundUnit == that.upperBoundUnit;
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

        @Override
        public int compareTo(Bucket right) {
            if (isInf()) {
                return right.isInf() ? 0 : 1;
            }

            if (right.isInf()) {
                return -1;
            }

            if (isNegativeInf()) {
                return right.isNegativeInf() ? 0 : -1;
            }

            if (right.isNegativeInf()) {
                return 1;
            }

            return Double.compare(upperBound, right.upperBound);
        }

        @Override
        public String toString() {
            return "Bucket{" +
                "upperBoundAsString='" + upperBoundAsString + '\'' +
                '}';
        }
    }

    Bucket MS_1_BUCKET = Bucket.of(1, MILLISECONDS);
    Bucket MS_5_BUCKET = Bucket.of(5, MILLISECONDS);
    Bucket MS_10_BUCKET = Bucket.of(10, MILLISECONDS);
    Bucket MS_15_BUCKET = Bucket.of(15, MILLISECONDS);
    Bucket MS_20_BUCKET = Bucket.of(20, MILLISECONDS);
    Bucket MS_25_BUCKET = Bucket.of(25, MILLISECONDS);
    Bucket MS_30_BUCKET = Bucket.of(30, MILLISECONDS);
    Bucket MS_35_BUCKET = Bucket.of(35, MILLISECONDS);
    Bucket MS_40_BUCKET = Bucket.of(40, MILLISECONDS);
    Bucket MS_45_BUCKET = Bucket.of(45, MILLISECONDS);
    Bucket MS_50_BUCKET = Bucket.of(50, MILLISECONDS);
    Bucket MS_75_BUCKET = Bucket.of(75, MILLISECONDS);
    Bucket MS_100_BUCKET = Bucket.of(100, MILLISECONDS);
    Bucket MS_250_BUCKET = Bucket.of(250, MILLISECONDS);
    Bucket MS_500_BUCKET = Bucket.of(500, MILLISECONDS);
    Bucket MS_750_BUCKET = Bucket.of(750, MILLISECONDS);

    Bucket SEC_1_BUCKET = Bucket.of(1, SECONDS);
    Bucket SEC_2p5_BUCKET = Bucket.of(2.5, SECONDS);
    Bucket SEC_5_BUCKET = Bucket.of(5, SECONDS);
    Bucket SEC_7p5_BUCKET = Bucket.of(7.5, SECONDS);
    Bucket SEC_10_BUCKET = Bucket.of(10, SECONDS);
    Bucket SEC_20_BUCKET = Bucket.of(20, SECONDS);
    Bucket SEC_30_BUCKET = Bucket.of(30, SECONDS);

    Bucket INF_BUCKET = Bucket.of(Double.POSITIVE_INFINITY);
    Bucket NEGATIVE_INF_BUCKET = Bucket.of(Double.NEGATIVE_INFINITY);

    class Buckets implements HistogramMeasurable {

        private final Set buckets;
        private final int hashCode;

        public Buckets(Set buckets) {
            checkArgument(
                buckets != null && !buckets.isEmpty(),
                "buckets is null or empty");

            this.buckets = buckets;
            this.hashCode = Objects.hash("Histogram.Buckets", buckets);
        }

        public static Buckets linear(long from, long step, long stepCount) {
            return linear(from, step, stepCount, null);
        }

        public static Buckets linear(long from, long step, long stepCount, TimeUnit unit) {
            return of(linearScale().from(from).steps(step, stepCount).withInf(), unit);
        }

        public static Buckets exp(long from, double factor, long stepCount) {
            return exp(from, factor, stepCount, null);
        }

        public static Buckets exp(long from, double factor, long stepCount, TimeUnit unit) {
            return of(expScale().from(from).factor(factor).steps(stepCount).withInf(), unit);
        }

        public static Buckets of(ScaleBuilder scaleBuilder) {
            return of(scaleBuilder, null);
        }

        public static Buckets of(ScaleBuilder scaleBuilder, TimeUnit unit) {
            return of(scaleBuilder.build(), unit);
        }

        public static Buckets of(Scale scale) {
            return of(scale, null);
        }

        public static Buckets of(Scale scale, TimeUnit unit) {
            return new Buckets(scale.points().stream().map(p -> Bucket.of(p.doubleValue(), unit)).collect(toSet()));
        }

        @Override
        public MeasurableType type() {
            return OBJECT;
        }

        public Set buckets() {
            return buckets;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }

            if (other == null || getClass() != other.getClass()) {
                return false;
            }

            Buckets that = (Buckets)other;

            if (hashCode != that.hashCode) {
                return false;
            }

            return buckets.equals(that.buckets);
        }

        @Override
        public int hashCode() {
            return hashCode;
        }
    }

    default void update(long value) {
        update(value, NO_LABEL_VALUES);
    }

    void update(long value, LabelValues labelValues);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy