Skip to content

Commit

Permalink
Merge branch 'main' into alanwest/otlp-span-limits
Browse files Browse the repository at this point in the history
  • Loading branch information
cijothomas authored Jul 28, 2022
2 parents e247d3d + 25cfa17 commit e524978
Show file tree
Hide file tree
Showing 11 changed files with 600 additions and 103 deletions.
12 changes: 0 additions & 12 deletions src/OpenTelemetry/Internal/MathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
// limitations under the License.
// </copyright>

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -136,15 +135,4 @@ public static bool IsFinite(double value)
return !double.IsInfinity(value) && !double.IsNaN(value);
#endif
}

public static string DoubleToString(double value)
{
var repr = Convert.ToString(BitConverter.DoubleToInt64Bits(value), 2);
return new string('0', 64 - repr.Length) + repr;
}

public static double DoubleFromString(string value)
{
return BitConverter.Int64BitsToDouble(Convert.ToInt64(value, 2));
}
}
77 changes: 72 additions & 5 deletions src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,68 @@ internal class ExponentialBucketHistogram
private int scale;
private double scalingFactor; // 2 ^ scale / log(2)

public ExponentialBucketHistogram(int scale, int maxBuckets = 160)
/// <summary>
/// Initializes a new instance of the <see cref="ExponentialBucketHistogram"/> class.
/// </summary>
/// <param name="maxBuckets">
/// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160.
/// </param>
public ExponentialBucketHistogram(int maxBuckets = 160)
: this(maxBuckets, 20)
{
}

internal ExponentialBucketHistogram(int maxBuckets, int scale)
{
Guard.ThrowIfOutOfRange(scale, min: -20, max: 20); // TODO: calculate the actual range
Guard.ThrowIfOutOfRange(maxBuckets, min: 1);
/*
The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]:
| Scale | Index Range |
| ----- | ------------------------- |
| < -11 | [-1, 0] |
| -11 | [-1, 0] |
| -10 | [-2, 0] |
| -9 | [-3, 1] |
| -8 | [-5, 3] |
| -7 | [-9, 7] |
| -6 | [-17, 15] |
| -5 | [-34, 31] |
| -4 | [-68, 63] |
| -3 | [-135, 127] |
| -2 | [-269, 255] |
| -1 | [-538, 511] |
| 0 | [-1075, 1023] |
| 1 | [-2149, 2047] |
| 2 | [-4297, 4095] |
| 3 | [-8593, 8191] |
| 4 | [-17185, 16383] |
| 5 | [-34369, 32767] |
| 6 | [-68737, 65535] |
| 7 | [-137473, 131071] |
| 8 | [-274945, 262143] |
| 9 | [-549889, 524287] |
| 10 | [-1099777, 1048575] |
| 11 | [-2199553, 2097151] |
| 12 | [-4399105, 4194303] |
| 13 | [-8798209, 8388607] |
| 14 | [-17596417, 16777215] |
| 15 | [-35192833, 33554431] |
| 16 | [-70385665, 67108863] |
| 17 | [-140771329, 134217727] |
| 18 | [-281542657, 268435455] |
| 19 | [-563085313, 536870911] |
| 20 | [-1126170625, 1073741823] |
| 21 | [underflow, 2147483647] |
| > 21 | [underflow, overflow] |
*/
Guard.ThrowIfOutOfRange(scale, min: -11, max: 20);

/*
Regardless of the scale, MapToIndex(1) will always be -1, so we need two buckets at minimum:
bucket[-1] = (1/base, 1]
bucket[0] = (1, base]
*/
Guard.ThrowIfOutOfRange(maxBuckets, min: 2);

this.Scale = scale;
this.PositiveBuckets = new CircularBufferBuckets(maxBuckets);
Expand Down Expand Up @@ -88,17 +146,26 @@ public int MapToIndex(double value)
Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount.");
Debug.Assert(!double.IsNegative(value), "IEEE-754 negative values should be normalized before calling this method.");

var bits = BitConverter.DoubleToInt64Bits(value);
var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */;

if (this.Scale > 0)
{
// TODO: do we really need this given the lookup table is needed for scale>0 anyways?
if (fraction == 0)
{
var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */);
return ((exp - 1023 /* exponent bias */) << this.Scale) - 1;
}

// TODO: due to precision issue, the values that are close to the bucket
// boundaries should be closely examined to avoid off-by-one.

return (int)Math.Ceiling(Math.Log(value) * this.scalingFactor) - 1;
}
else
{
var bits = BitConverter.DoubleToInt64Bits(value);
var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */);
var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */;

if (exp == 0)
{
Expand Down
56 changes: 56 additions & 0 deletions test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,39 @@ void ConfigureTestServices(IServiceCollection services)
Assert.Equal(shouldEnrichBeCalled, enrichCalled);
}

[Fact(Skip = "Changes pending on instrumentation")]
public async Task ActivitiesStartedInMiddlewareShouldNotBeUpdatedByInstrumentation()
{
var exportedItems = new List<Activity>();

var activitySourceName = "TestMiddlewareActivitySource";
var activityName = "TestMiddlewareActivity";

// Arrange
using (var client = this.factory
.WithWebHostBuilder(builder =>
builder.ConfigureTestServices((IServiceCollection services) =>
{
services.AddSingleton<ActivityMiddleware.ActivityMiddlewareImpl>(new TestActivityMiddlewareImpl(activitySourceName, activityName));
services.AddOpenTelemetryTracing((builder) => builder.AddAspNetCoreInstrumentation()
.AddSource(activitySourceName)
.AddInMemoryExporter(exportedItems));
}))
.CreateClient())
{
var response = await client.GetAsync("/api/values/2");
response.EnsureSuccessStatusCode();
WaitForActivityExport(exportedItems, 2);
}

Assert.Equal(2, exportedItems.Count);

var middlewareActivity = exportedItems[0];

// Middleware activity name should not be changed
Assert.Equal(activityName, middlewareActivity.DisplayName);
}

public void Dispose()
{
this.tracerProvider?.Dispose();
Expand Down Expand Up @@ -661,5 +694,28 @@ public override void OnStopActivity(Activity activity, object payload)
this.OnStopActivityCallback?.Invoke(activity, payload);
}
}

private class TestActivityMiddlewareImpl : ActivityMiddleware.ActivityMiddlewareImpl
{
private ActivitySource activitySource;
private Activity activity;
private string activityName;

public TestActivityMiddlewareImpl(string activitySourceName, string activityName)
{
this.activitySource = new ActivitySource(activitySourceName);
this.activityName = activityName;
}

public override void PreProcess(HttpContext context)
{
this.activity = this.activitySource.StartActivity(this.activityName);
}

public override void PostProcess(HttpContext context)
{
this.activity?.Stop();
}
}
}
}
47 changes: 32 additions & 15 deletions test/OpenTelemetry.Tests.Stress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,32 @@ Running (concurrency = 1), press <Esc> to stop...
The stress test metrics are exposed via
[PrometheusExporter](../../src/OpenTelemetry.Exporter.Prometheus/README.md),
which can be accessed via
[http://localhost:9184/metrics/](http://localhost:9184/metrics/):
[http://localhost:9184/metrics/](http://localhost:9184/metrics/).

```text
# TYPE Process_NonpagedSystemMemorySize64 gauge
Process_NonpagedSystemMemorySize64 31651 1637385964580
# TYPE Process_PagedSystemMemorySize64 gauge
Process_PagedSystemMemorySize64 238672 1637385964580
# TYPE Process_PagedMemorySize64 gauge
Process_PagedMemorySize64 16187392 1637385964580
# TYPE Process_WorkingSet64 gauge
Process_WorkingSet64 29753344 1637385964580
Following shows a section of the metrics exposed in prometheus format:

# TYPE Process_VirtualMemorySize64 gauge
Process_VirtualMemorySize64 2204045848576 1637385964580
```text
# HELP OpenTelemetry_Tests_Stress_Loops The total number of `Run()` invocations that are completed.
# TYPE OpenTelemetry_Tests_Stress_Loops counter
OpenTelemetry_Tests_Stress_Loops 1844902947 1658950184752
# HELP OpenTelemetry_Tests_Stress_LoopsPerSecond The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds.
# TYPE OpenTelemetry_Tests_Stress_LoopsPerSecond gauge
OpenTelemetry_Tests_Stress_LoopsPerSecond 9007731.132075472 1658950184752
# HELP OpenTelemetry_Tests_Stress_CpuCyclesPerLoop The average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds.
# TYPE OpenTelemetry_Tests_Stress_CpuCyclesPerLoop gauge
OpenTelemetry_Tests_Stress_CpuCyclesPerLoop 3008 1658950184752
# HELP process_runtime_dotnet_gc_collections_count Number of garbage collections that have occurred since process start.
# TYPE process_runtime_dotnet_gc_collections_count counter
process_runtime_dotnet_gc_collections_count{generation="gen2"} 0 1658950184752
process_runtime_dotnet_gc_collections_count{generation="gen1"} 0 1658950184752
process_runtime_dotnet_gc_collections_count{generation="gen0"} 0 1658950184752
# HELP process_runtime_dotnet_gc_allocations_size_bytes Count of bytes allocated on the managed GC heap since the process start. .NET objects are allocated from this heap. Object allocations from unmanaged languages such as C/C++ do not use this heap.
# TYPE process_runtime_dotnet_gc_allocations_size_bytes counter
process_runtime_dotnet_gc_allocations_size_bytes 5485192 1658950184752
```

## Writing your own stress test
Expand Down Expand Up @@ -92,6 +101,13 @@ Add the [`Skeleton.cs`](./Skeleton.cs) file to your `*.csproj` file:
</ItemGroup>
```

Add the following packages to the project:

```shell
dotnet add package OpenTelemetry.Exporter.Prometheus --prerelease
dotnet add package OpenTelemetry.Instrumentation.Runtime --prerelease
```

Now you are ready to run your own stress test.

Some useful notes:
Expand All @@ -114,3 +130,4 @@ Some useful notes:
sliding window of few hundreds of milliseconds.
* `CPU Cycles/Loop` represents the average CPU cycles for each `Run()`
invocation, based on a small sliding window of few hundreds of milliseconds.
* `Runaway Time` represents the runaway time (seconds) since the test started.
12 changes: 3 additions & 9 deletions test/OpenTelemetry.Tests.Stress/Skeleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0)
() => dLoopsPerSecond,
description: "The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds.");
var dCpuCyclesPerLoop = 0D;
#if NET462
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
#else

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
#endif
{
meter.CreateObservableGauge(
"OpenTelemetry.Tests.Stress.CpuCyclesPerLoop",
Expand Down Expand Up @@ -146,7 +143,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0)
dLoopsPerSecond = (double)nLoops / ((double)watch.ElapsedMilliseconds / 1000.0);
dCpuCyclesPerLoop = nLoops == 0 ? 0 : nCpuCycles / nLoops;

output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}";
output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}, RunwayTime (Seconds): {watchForTotal.Elapsed.TotalSeconds:n0} ";
Console.Title = output;
}
},
Expand All @@ -169,6 +166,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0)
var cntCpuCyclesTotal = GetCpuCycles();
var cpuCyclesPerLoopTotal = cntLoopsTotal == 0 ? 0 : cntCpuCyclesTotal / cntLoopsTotal;
Console.WriteLine("Stopping the stress test...");
Console.WriteLine($"* Total Runaway Time (seconds) {watchForTotal.Elapsed.TotalSeconds:n0}");
Console.WriteLine($"* Total Loops: {cntLoopsTotal:n0}");
Console.WriteLine($"* Average Loops/Second: {totalLoopsPerSecond:n0}");
Console.WriteLine($"* Average CPU Cycles/Loop: {cpuCyclesPerLoopTotal:n0}");
Expand All @@ -180,11 +178,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0)

private static ulong GetCpuCycles()
{
#if NET462
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
#else
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
#endif
{
return 0;
}
Expand Down
Loading

0 comments on commit e524978

Please sign in to comment.