From 19b12764905b3e681285218053ba0fef455d7e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Wed, 27 Sep 2023 13:23:37 +0200 Subject: [PATCH] Optimize DotvvmMetrics.ExponentialBuckets The stupid prometheus-net library call this function for every single datapoint... I didn't want to cache the result arrays, as we'd have to undo that optimization when we add some configuration arguments to the TryGetRecommendedBuckets method. --- .../Framework/Hosting/DotvvmMetrics.cs | 38 +++++++++++++------ src/Tests/Runtime/MetricsTests.cs | 29 ++++++++++++++ 2 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/Tests/Runtime/MetricsTests.cs diff --git a/src/Framework/Framework/Hosting/DotvvmMetrics.cs b/src/Framework/Framework/Hosting/DotvvmMetrics.cs index 918edb5f9d..efabc3943d 100644 --- a/src/Framework/Framework/Hosting/DotvvmMetrics.cs +++ b/src/Framework/Framework/Hosting/DotvvmMetrics.cs @@ -112,24 +112,24 @@ public static class DotvvmMetrics var secStart = 1.0 / 128.0; // about 10ms, so that 1second is a boundary if (instrument == ResourceServeDuration) - return ExponentialBuckets(secStart, 2, 0.5); + return ExponentialBuckets(secStart, 0.5); if (instrument == ResourceServeDuration) - return ExponentialBuckets(secStart, 2, 0.5); + return ExponentialBuckets(secStart, 0.5); if (instrument == ResourceServeDuration) - return ExponentialBuckets(secStart, 2, 1); + return ExponentialBuckets(secStart, 1); if (instrument == RequestDuration || instrument == CommandInvocationDuration || instrument == StaticCommandInvocationDuration) - return ExponentialBuckets(secStart, 2, 65); + return ExponentialBuckets(secStart, 65); if (instrument.Unit == "seconds") - return ExponentialBuckets(secStart, 2, 2.0); + return ExponentialBuckets(secStart, 2.0); if (instrument.Unit == "bytes") - return ExponentialBuckets(1024, 2, 130 * 1024 * 1024); // 1KB ... 128MB + return ExponentialBuckets(1024, 130 * 1024 * 1024); // 1KB ... 128MB - return ExponentialBuckets(secStart, 2, 10); + return ExponentialBuckets(secStart, 10); } // The Counter from metrics doesn't count anything when there isn't a listener. @@ -143,12 +143,26 @@ internal class BareCounters public static long DotvvmPropertyInitialized = 0; } - internal static double[] ExponentialBuckets(double start, double factor, double end) + internal static int IntegerLog2(double value) { - return Enumerable.Range(0, 1000) - .Select(i => start * Math.Pow(factor, i)) - .TakeWhile(b => b <= end) - .ToArray(); + // float64 is stored as 1 sign bit, 11 exponent bits, 52 mantissa bits + // where the exponent is essentially the integer logarithm we want + var bits = BitConverter.DoubleToInt64Bits(value); + var exponent = (int)((bits >> 52) & 0x7FF); + // the exponent is a signed integer, stored as unsigned with 1023 bias + return exponent - 1023; + } + + internal static double[] ExponentialBuckets(double start, double end) + { + var buckets = new double[IntegerLog2(end / start) + 1]; + var bucket = start; + for (int i = 0; i < buckets.Length; i++) + { + buckets[i] = bucket; + bucket *= 2; + } + return buckets; } internal static KeyValuePair RouteLabel(this IDotvvmRequestContext context) => diff --git a/src/Tests/Runtime/MetricsTests.cs b/src/Tests/Runtime/MetricsTests.cs new file mode 100644 index 0000000000..38fd93c8f0 --- /dev/null +++ b/src/Tests/Runtime/MetricsTests.cs @@ -0,0 +1,29 @@ +using System; +using DotVVM.Framework.Hosting; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DotVVM.Framework.Tests.Runtime +{ + [TestClass] + public class MetricsTests + { + [TestMethod] + public void IntLog() + { + for (int i = 1; i < 100; i++) + { + var log = DotvvmMetrics.IntegerLog2(i); + var expectedLog = (int)Math.Log(i, 2); + Assert.AreEqual(expectedLog, log); + } + } + + [TestMethod] + public void ExponentialBuckets() + { + XAssert.Equal(new double[] { 1, 2, 4, 8, 16, 32, 64 }, DotvvmMetrics.ExponentialBuckets(1, 64)); + XAssert.Equal(new double[] { 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64 }, DotvvmMetrics.TryGetRecommendedBuckets(DotvvmMetrics.RequestDuration)); + + } + } +}