Skip to content

Commit

Permalink
Optimize DotvvmMetrics.ExponentialBuckets
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
exyi committed Sep 27, 2023
1 parent 9b8d417 commit 19b1276
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 12 deletions.
38 changes: 26 additions & 12 deletions src/Framework/Framework/Hosting/DotvvmMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<string, object?> RouteLabel(this IDotvvmRequestContext context) =>
Expand Down
29 changes: 29 additions & 0 deletions src/Tests/Runtime/MetricsTests.cs
Original file line number Diff line number Diff line change
@@ -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));

}
}
}

0 comments on commit 19b1276

Please sign in to comment.