package net.minecraft.util.profiling.metrics; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import it.unimi.dsi.fastutil.ints.Int2DoubleMap; import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; import java.util.Locale; import java.util.function.Consumer; import java.util.function.DoubleSupplier; import java.util.function.ToDoubleFunction; import org.jetbrains.annotations.Nullable; public class MetricSampler { private final String name; private final MetricCategory category; private final DoubleSupplier sampler; private final ByteBuf ticks; private final ByteBuf values; private volatile boolean isRunning; @Nullable private final Runnable beforeTick; @Nullable final MetricSampler.ThresholdTest thresholdTest; private double currentValue; protected MetricSampler( String name, MetricCategory category, DoubleSupplier sampler, @Nullable Runnable beforeTick, @Nullable MetricSampler.ThresholdTest thresholdTest ) { this.name = name; this.category = category; this.beforeTick = beforeTick; this.sampler = sampler; this.thresholdTest = thresholdTest; this.values = ByteBufAllocator.DEFAULT.buffer(); this.ticks = ByteBufAllocator.DEFAULT.buffer(); this.isRunning = true; } public static MetricSampler create(String name, MetricCategory category, DoubleSupplier sampler) { return new MetricSampler(name, category, sampler, null, null); } public static MetricSampler create(String name, MetricCategory category, T context, ToDoubleFunction sampler) { return builder(name, category, sampler, context).build(); } public static MetricSampler.MetricSamplerBuilder builder(String name, MetricCategory category, ToDoubleFunction sampler, T context) { return new MetricSampler.MetricSamplerBuilder<>(name, category, sampler, context); } public void onStartTick() { if (!this.isRunning) { throw new IllegalStateException("Not running"); } else { if (this.beforeTick != null) { this.beforeTick.run(); } } } public void onEndTick(int tickTime) { this.verifyRunning(); this.currentValue = this.sampler.getAsDouble(); this.values.writeDouble(this.currentValue); this.ticks.writeInt(tickTime); } public void onFinished() { this.verifyRunning(); this.values.release(); this.ticks.release(); this.isRunning = false; } private void verifyRunning() { if (!this.isRunning) { throw new IllegalStateException(String.format(Locale.ROOT, "Sampler for metric %s not started!", this.name)); } } DoubleSupplier getSampler() { return this.sampler; } public String getName() { return this.name; } public MetricCategory getCategory() { return this.category; } public MetricSampler.SamplerResult result() { Int2DoubleMap int2DoubleMap = new Int2DoubleOpenHashMap(); int i = Integer.MIN_VALUE; int j = Integer.MIN_VALUE; while (this.values.isReadable(8)) { int k = this.ticks.readInt(); if (i == Integer.MIN_VALUE) { i = k; } int2DoubleMap.put(k, this.values.readDouble()); j = k; } return new MetricSampler.SamplerResult(i, j, int2DoubleMap); } public boolean triggersThreshold() { return this.thresholdTest != null && this.thresholdTest.test(this.currentValue); } public boolean equals(Object object) { if (this == object) { return true; } else if (object != null && this.getClass() == object.getClass()) { MetricSampler metricSampler = (MetricSampler)object; return this.name.equals(metricSampler.name) && this.category.equals(metricSampler.category); } else { return false; } } public int hashCode() { return this.name.hashCode(); } public static class MetricSamplerBuilder { private final String name; private final MetricCategory category; private final DoubleSupplier sampler; private final T context; @Nullable private Runnable beforeTick; @Nullable private MetricSampler.ThresholdTest thresholdTest; public MetricSamplerBuilder(String name, MetricCategory category, ToDoubleFunction sampler, T context) { this.name = name; this.category = category; this.sampler = () -> sampler.applyAsDouble(context); this.context = context; } public MetricSampler.MetricSamplerBuilder withBeforeTick(Consumer beforeTick) { this.beforeTick = () -> beforeTick.accept(this.context); return this; } public MetricSampler.MetricSamplerBuilder withThresholdAlert(MetricSampler.ThresholdTest thresholdTest) { this.thresholdTest = thresholdTest; return this; } public MetricSampler build() { return new MetricSampler(this.name, this.category, this.sampler, this.beforeTick, this.thresholdTest); } } public static class SamplerResult { private final Int2DoubleMap recording; private final int firstTick; private final int lastTick; public SamplerResult(int firstTick, int lastTick, Int2DoubleMap recording) { this.firstTick = firstTick; this.lastTick = lastTick; this.recording = recording; } public double valueAtTick(int tick) { return this.recording.get(tick); } public int getFirstTick() { return this.firstTick; } public int getLastTick() { return this.lastTick; } } public interface ThresholdTest { boolean test(double value); } public static class ValueIncreasedByPercentage implements MetricSampler.ThresholdTest { private final float percentageIncreaseThreshold; private double previousValue = Double.MIN_VALUE; public ValueIncreasedByPercentage(float percentageIncreaseThreshold) { this.percentageIncreaseThreshold = percentageIncreaseThreshold; } @Override public boolean test(double d) { boolean bl; if (this.previousValue != Double.MIN_VALUE && !(d <= this.previousValue)) { bl = (d - this.previousValue) / this.previousValue >= this.percentageIncreaseThreshold; } else { bl = false; } this.previousValue = d; return bl; } } }