[tools] Stress test improvements (#5381)
This commit is contained in:
parent
7e0213ddbd
commit
73b6e30c1b
|
|
@ -16,6 +16,7 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Console" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)]
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Tests.Stress.Metrics" + AssemblyInfo.PublicKey)]
|
||||
#endif
|
||||
|
||||
#if SIGNED
|
||||
|
|
|
|||
|
|
@ -3,21 +3,13 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
|
||||
<!-- this is temporary. will remove in future PR. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\OpenTelemetry.Tests.Stress.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\Skeleton.cs" Link="Includes\Skeleton.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\Utils.cs" Link="Includes\Utils.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +1,55 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public partial class Program
|
||||
public static class Program
|
||||
{
|
||||
private static ILogger logger;
|
||||
private static Payload payload = new Payload();
|
||||
|
||||
public static void Main()
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.AddOpenTelemetry(options =>
|
||||
{
|
||||
options.AddProcessor(new DummyProcessor());
|
||||
});
|
||||
});
|
||||
|
||||
logger = loggerFactory.CreateLogger<Program>();
|
||||
|
||||
Stress(prometheusPort: 9464);
|
||||
return StressTestFactory.RunSynchronously<LogsStressTest>(args);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static void Run()
|
||||
private sealed class LogsStressTest : StressTest<StressTestOptions>
|
||||
{
|
||||
logger.Log(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: 2,
|
||||
state: payload,
|
||||
exception: null,
|
||||
formatter: (state, ex) => string.Empty);
|
||||
private static readonly Payload Payload = new();
|
||||
private readonly ILoggerFactory loggerFactory;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public LogsStressTest(StressTestOptions options)
|
||||
: base(options)
|
||||
{
|
||||
this.loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.AddOpenTelemetry(options =>
|
||||
{
|
||||
options.AddProcessor(new DummyProcessor());
|
||||
});
|
||||
});
|
||||
|
||||
this.logger = this.loggerFactory.CreateLogger<LogsStressTest>();
|
||||
}
|
||||
|
||||
protected override void RunWorkItemInParallel()
|
||||
{
|
||||
this.logger.Log(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: 2,
|
||||
state: Payload,
|
||||
exception: null,
|
||||
formatter: (state, ex) => string.Empty);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
this.loggerFactory.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,23 +3,15 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
|
||||
<!-- this is temporary. will remove in future PR. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\OpenTelemetry.Tests.Stress.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\Utils.cs" Link="Includes\Utils.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\Skeleton.cs" Link="Includes\Skeleton.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,65 +2,140 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using CommandLine;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public partial class Program
|
||||
public static class Program
|
||||
{
|
||||
private const int ArraySize = 10;
|
||||
|
||||
// Note: Uncomment the below line if you want to run Histogram stress test
|
||||
private const int MaxHistogramMeasurement = 1000;
|
||||
|
||||
private static readonly Meter TestMeter = new(Utils.GetCurrentMethodName());
|
||||
private static readonly Counter<long> TestCounter = TestMeter.CreateCounter<long>("TestCounter");
|
||||
private static readonly string[] DimensionValues = new string[ArraySize];
|
||||
private static readonly ThreadLocal<Random> ThreadLocalRandom = new(() => new Random());
|
||||
|
||||
// Note: Uncomment the below line if you want to run Histogram stress test
|
||||
private static readonly Histogram<long> TestHistogram = TestMeter.CreateHistogram<long>("TestHistogram");
|
||||
|
||||
public static void Main()
|
||||
private enum MetricsStressTestType
|
||||
{
|
||||
for (int i = 0; i < ArraySize; i++)
|
||||
/// <summary>Histogram.</summary>
|
||||
Histogram,
|
||||
|
||||
/// <summary>Counter.</summary>
|
||||
Counter,
|
||||
}
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
return StressTestFactory.RunSynchronously<MetricsStressTest, MetricsStressTestOptions>(args);
|
||||
}
|
||||
|
||||
private sealed class MetricsStressTest : StressTest<MetricsStressTestOptions>
|
||||
{
|
||||
private const int ArraySize = 10;
|
||||
private const int MaxHistogramMeasurement = 1000;
|
||||
|
||||
private static readonly Meter TestMeter = new(Utils.GetCurrentMethodName());
|
||||
private static readonly Histogram<long> TestHistogram = TestMeter.CreateHistogram<long>("TestHistogram");
|
||||
private static readonly Counter<long> TestCounter = TestMeter.CreateCounter<long>("TestCounter");
|
||||
private static readonly string[] DimensionValues = new string[ArraySize];
|
||||
private static readonly ThreadLocal<Random> ThreadLocalRandom = new(() => new Random());
|
||||
private readonly MeterProvider meterProvider;
|
||||
|
||||
static MetricsStressTest()
|
||||
{
|
||||
DimensionValues[i] = $"DimValue{i}";
|
||||
for (int i = 0; i < ArraySize; i++)
|
||||
{
|
||||
DimensionValues[i] = $"DimValue{i}";
|
||||
}
|
||||
}
|
||||
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter(TestMeter.Name)
|
||||
public MetricsStressTest(MetricsStressTestOptions options)
|
||||
: base(options)
|
||||
{
|
||||
var builder = Sdk.CreateMeterProviderBuilder().AddMeter(TestMeter.Name);
|
||||
|
||||
// .SetExemplarFilter(new AlwaysOnExemplarFilter())
|
||||
.AddPrometheusHttpListener(
|
||||
options => options.UriPrefixes = new string[] { $"http://localhost:9185/" })
|
||||
.Build();
|
||||
if (options.PrometheusTestMetricsPort != 0)
|
||||
{
|
||||
builder.AddPrometheusHttpListener(o => o.UriPrefixes = new string[] { $"http://localhost:{options.PrometheusTestMetricsPort}/" });
|
||||
}
|
||||
|
||||
Stress(prometheusPort: 9464);
|
||||
if (options.EnableExemplars)
|
||||
{
|
||||
builder.SetExemplarFilter(new AlwaysOnExemplarFilter());
|
||||
}
|
||||
|
||||
if (options.AddViewToFilterTags)
|
||||
{
|
||||
builder
|
||||
.AddView("TestCounter", new MetricStreamConfiguration { TagKeys = new string[] { "DimName1" } })
|
||||
.AddView("TestHistogram", new MetricStreamConfiguration { TagKeys = new string[] { "DimName1" } });
|
||||
}
|
||||
|
||||
if (options.AddOtlpExporter)
|
||||
{
|
||||
builder.AddOtlpExporter((exporterOptions, readerOptions) =>
|
||||
{
|
||||
readerOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = options.OtlpExporterExportIntervalMilliseconds;
|
||||
});
|
||||
}
|
||||
|
||||
this.meterProvider = builder.Build();
|
||||
}
|
||||
|
||||
protected override void WriteRunInformationToConsole()
|
||||
{
|
||||
if (this.Options.PrometheusTestMetricsPort != 0)
|
||||
{
|
||||
Console.Write($", testPrometheusEndpoint = http://localhost:{this.Options.PrometheusTestMetricsPort}/metrics/");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RunWorkItemInParallel()
|
||||
{
|
||||
var random = ThreadLocalRandom.Value!;
|
||||
if (this.Options.TestType == MetricsStressTestType.Histogram)
|
||||
{
|
||||
TestHistogram.Record(
|
||||
random.Next(MaxHistogramMeasurement),
|
||||
new("DimName1", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName2", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName3", DimensionValues[random.Next(0, ArraySize)]));
|
||||
}
|
||||
else if (this.Options.TestType == MetricsStressTestType.Counter)
|
||||
{
|
||||
TestCounter.Add(
|
||||
100,
|
||||
new("DimName1", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName2", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName3", DimensionValues[random.Next(0, ArraySize)]));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
this.meterProvider.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Uncomment the below lines if you want to run Counter stress test
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// protected static void Run()
|
||||
// {
|
||||
// var random = ThreadLocalRandom.Value;
|
||||
// TestCounter.Add(
|
||||
// 100,
|
||||
// new("DimName1", DimensionValues[random.Next(0, ArraySize)]),
|
||||
// new("DimName2", DimensionValues[random.Next(0, ArraySize)]),
|
||||
// new("DimName3", DimensionValues[random.Next(0, ArraySize)]));
|
||||
// }
|
||||
|
||||
// Note: Uncomment the below lines if you want to run Histogram stress test
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static void Run()
|
||||
private sealed class MetricsStressTestOptions : StressTestOptions
|
||||
{
|
||||
var random = ThreadLocalRandom.Value;
|
||||
TestHistogram.Record(
|
||||
random.Next(MaxHistogramMeasurement),
|
||||
new("DimName1", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName2", DimensionValues[random.Next(0, ArraySize)]),
|
||||
new("DimName3", DimensionValues[random.Next(0, ArraySize)]));
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
[Option('t', "type", HelpText = "The metrics stress test type to run. Valid values: [Histogram, Counter]. Default value: Histogram.", Required = false)]
|
||||
public MetricsStressTestType TestType { get; set; } = MetricsStressTestType.Histogram;
|
||||
|
||||
[Option('m', "metrics_port", HelpText = "The Prometheus http listener port where Prometheus will be exposed for retrieving test metrics while the stress test is running. Set to '0' to disable. Default value: 9185.", Required = false)]
|
||||
public int PrometheusTestMetricsPort { get; set; } = 9185;
|
||||
|
||||
[Option('v', "view", HelpText = "Whether or not a view should be configured to filter tags for the stress test. Default value: False.", Required = false)]
|
||||
public bool AddViewToFilterTags { get; set; }
|
||||
|
||||
[Option('o', "otlp", HelpText = "Whether or not an OTLP exporter should be added for the stress test. Default value: False.", Required = false)]
|
||||
public bool AddOtlpExporter { get; set; }
|
||||
|
||||
[Option('i', "interval", HelpText = "The OTLP exporter export interval in milliseconds. Default value: 5000.", Required = false)]
|
||||
public int OtlpExporterExportIntervalMilliseconds { get; set; } = 5000;
|
||||
|
||||
[Option('e', "exemplars", HelpText = "Whether or not to enable exemplars for the stress test. Default value: False.", Required = false)]
|
||||
public bool EnableExemplars { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,17 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\Skeleton.cs" Link="Includes\Skeleton.cs" />
|
||||
<ProjectReference Include="$(RepoRoot)\test\OpenTelemetry.Tests.Stress\OpenTelemetry.Tests.Stress.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,31 +2,45 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public partial class Program
|
||||
public static class Program
|
||||
{
|
||||
private static readonly ActivitySource ActivitySource = new ActivitySource("OpenTelemetry.Tests.Stress");
|
||||
|
||||
public static void Main()
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(ActivitySource.Name)
|
||||
.Build();
|
||||
|
||||
Stress(prometheusPort: 9464);
|
||||
return StressTestFactory.RunSynchronously<TracesStressTest>(args);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static void Run()
|
||||
private sealed class TracesStressTest : StressTest<StressTestOptions>
|
||||
{
|
||||
using (var activity = ActivitySource.StartActivity("test"))
|
||||
private static readonly ActivitySource ActivitySource = new("OpenTelemetry.Tests.Stress");
|
||||
private readonly TracerProvider tracerProvider;
|
||||
|
||||
public TracesStressTest(StressTestOptions options)
|
||||
: base(options)
|
||||
{
|
||||
this.tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSource(ActivitySource.Name)
|
||||
.Build();
|
||||
}
|
||||
|
||||
protected override void RunWorkItemInParallel()
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("test");
|
||||
|
||||
activity?.SetTag("foo", "value");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
this.tracerProvider.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public partial class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
Stress(concurrency: 1, prometheusPort: 9464);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static void Run()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
|
||||
<PackageReference Include="System.Text.Json" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
return StressTestFactory.RunSynchronously<DemoStressTest>(args);
|
||||
}
|
||||
|
||||
private sealed class DemoStressTest : StressTest<StressTestOptions>
|
||||
{
|
||||
public DemoStressTest(StressTestOptions options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RunWorkItemInParallel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,10 @@ process_runtime_dotnet_gc_allocations_size_bytes 5485192 1658950184752
|
|||
|
||||
## Writing your own stress test
|
||||
|
||||
> [!WARNING]
|
||||
> These instructions are out of date and should NOT be followed. They will be
|
||||
updated soon.
|
||||
|
||||
Create a simple console application with the following code:
|
||||
|
||||
```csharp
|
||||
|
|
@ -93,7 +97,7 @@ public partial class Program
|
|||
}
|
||||
```
|
||||
|
||||
Add the [`Skeleton.cs`](./Skeleton.cs) file to your `*.csproj` file:
|
||||
Add the Skeleton.cs file to your `*.csproj` file:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public partial class Program
|
||||
{
|
||||
private static volatile bool bContinue = true;
|
||||
private static volatile string output = "Test results not available yet.";
|
||||
|
||||
static Program()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Stress(int concurrency = 0, int prometheusPort = 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("***WARNING*** The current build is DEBUG which may affect timing!");
|
||||
Console.WriteLine();
|
||||
#endif
|
||||
|
||||
if (concurrency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(concurrency), "concurrency level should be a non-negative number.");
|
||||
}
|
||||
|
||||
if (concurrency == 0)
|
||||
{
|
||||
concurrency = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
using var meter = new Meter("OpenTelemetry.Tests.Stress." + Guid.NewGuid().ToString("D"));
|
||||
var cntLoopsTotal = 0UL;
|
||||
meter.CreateObservableCounter(
|
||||
"OpenTelemetry.Tests.Stress.Loops",
|
||||
() => unchecked((long)cntLoopsTotal),
|
||||
description: "The total number of `Run()` invocations that are completed.");
|
||||
var dLoopsPerSecond = 0D;
|
||||
meter.CreateObservableGauge(
|
||||
"OpenTelemetry.Tests.Stress.LoopsPerSecond",
|
||||
() => dLoopsPerSecond,
|
||||
description: "The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds.");
|
||||
var dCpuCyclesPerLoop = 0D;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
meter.CreateObservableGauge(
|
||||
"OpenTelemetry.Tests.Stress.CpuCyclesPerLoop",
|
||||
() => dCpuCyclesPerLoop,
|
||||
description: "The average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds.");
|
||||
}
|
||||
|
||||
using var meterProvider = prometheusPort != 0 ? Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter(meter.Name)
|
||||
.AddRuntimeInstrumentation()
|
||||
.AddPrometheusHttpListener(
|
||||
options => options.UriPrefixes = new string[] { $"http://localhost:{prometheusPort}/" })
|
||||
.Build() : null;
|
||||
|
||||
var statistics = new long[concurrency];
|
||||
var watchForTotal = Stopwatch.StartNew();
|
||||
|
||||
Parallel.Invoke(
|
||||
() =>
|
||||
{
|
||||
Console.Write($"Running (concurrency = {concurrency}");
|
||||
|
||||
if (prometheusPort != 0)
|
||||
{
|
||||
Console.Write($", prometheusEndpoint = http://localhost:{prometheusPort}/metrics/");
|
||||
}
|
||||
|
||||
Console.WriteLine("), press <Esc> to stop...");
|
||||
|
||||
var bOutput = false;
|
||||
var watch = new Stopwatch();
|
||||
while (true)
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
var key = Console.ReadKey(true).Key;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case ConsoleKey.Enter:
|
||||
Console.WriteLine(string.Format("{0} {1}", DateTime.UtcNow.ToString("O"), output));
|
||||
break;
|
||||
case ConsoleKey.Escape:
|
||||
bContinue = false;
|
||||
return;
|
||||
case ConsoleKey.Spacebar:
|
||||
bOutput = !bOutput;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bOutput)
|
||||
{
|
||||
Console.WriteLine(string.Format("{0} {1}", DateTime.UtcNow.ToString("O"), output));
|
||||
}
|
||||
|
||||
var cntLoopsOld = (ulong)statistics.Sum();
|
||||
var cntCpuCyclesOld = GetCpuCycles();
|
||||
|
||||
watch.Restart();
|
||||
Thread.Sleep(200);
|
||||
watch.Stop();
|
||||
|
||||
cntLoopsTotal = (ulong)statistics.Sum();
|
||||
var cntCpuCyclesNew = GetCpuCycles();
|
||||
|
||||
var nLoops = cntLoopsTotal - cntLoopsOld;
|
||||
var nCpuCycles = cntCpuCyclesNew - cntCpuCyclesOld;
|
||||
|
||||
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}, RunwayTime (Seconds): {watchForTotal.Elapsed.TotalSeconds:n0} ";
|
||||
Console.Title = output;
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
Parallel.For(0, concurrency, (i) =>
|
||||
{
|
||||
statistics[i] = 0;
|
||||
while (bContinue)
|
||||
{
|
||||
Run();
|
||||
statistics[i]++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watchForTotal.Stop();
|
||||
cntLoopsTotal = (ulong)statistics.Sum();
|
||||
var totalLoopsPerSecond = (double)cntLoopsTotal / ((double)watchForTotal.ElapsedMilliseconds / 1000.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}");
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool QueryProcessCycleTime(IntPtr hProcess, out ulong cycles);
|
||||
|
||||
private static ulong GetCpuCycles()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!QueryProcessCycleTime((IntPtr)(-1), out var cycles))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cycles;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public abstract class StressTest<T> : IDisposable
|
||||
where T : StressTestOptions
|
||||
{
|
||||
private volatile bool bContinue = true;
|
||||
private volatile string output = "Test results not available yet.";
|
||||
|
||||
protected StressTest(T options)
|
||||
{
|
||||
this.Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public T Options { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void RunSynchronously()
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("***WARNING*** The current build is DEBUG which may affect timing!");
|
||||
Console.WriteLine();
|
||||
#endif
|
||||
|
||||
var options = this.Options;
|
||||
|
||||
if (options.Concurrency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(options.Concurrency), "Concurrency level should be a non-negative number.");
|
||||
}
|
||||
|
||||
if (options.Concurrency == 0)
|
||||
{
|
||||
options.Concurrency = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
using var meter = new Meter("OpenTelemetry.Tests.Stress." + Guid.NewGuid().ToString("D"));
|
||||
var cntLoopsTotal = 0UL;
|
||||
meter.CreateObservableCounter(
|
||||
"OpenTelemetry.Tests.Stress.Loops",
|
||||
() => unchecked((long)cntLoopsTotal),
|
||||
description: "The total number of `Run()` invocations that are completed.");
|
||||
var dLoopsPerSecond = 0D;
|
||||
meter.CreateObservableGauge(
|
||||
"OpenTelemetry.Tests.Stress.LoopsPerSecond",
|
||||
() => dLoopsPerSecond,
|
||||
description: "The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds.");
|
||||
var dCpuCyclesPerLoop = 0D;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
meter.CreateObservableGauge(
|
||||
"OpenTelemetry.Tests.Stress.CpuCyclesPerLoop",
|
||||
() => dCpuCyclesPerLoop,
|
||||
description: "The average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds.");
|
||||
}
|
||||
|
||||
using var meterProvider = options.PrometheusInternalMetricsPort != 0 ? Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter(meter.Name)
|
||||
.AddRuntimeInstrumentation()
|
||||
.AddPrometheusHttpListener(o => o.UriPrefixes = new string[] { $"http://localhost:{options.PrometheusInternalMetricsPort}/" })
|
||||
.Build() : null;
|
||||
|
||||
var statistics = new long[options.Concurrency];
|
||||
var watchForTotal = Stopwatch.StartNew();
|
||||
|
||||
TimeSpan? duration = options.DurationSeconds > 0
|
||||
? TimeSpan.FromSeconds(options.DurationSeconds)
|
||||
: null;
|
||||
|
||||
Parallel.Invoke(
|
||||
() =>
|
||||
{
|
||||
Console.WriteLine($"Options: {JsonSerializer.Serialize(options)}");
|
||||
Console.WriteLine($"Run {Process.GetCurrentProcess().ProcessName}.exe --help to see available options.");
|
||||
Console.Write($"Running (concurrency = {options.Concurrency}");
|
||||
|
||||
if (options.PrometheusInternalMetricsPort != 0)
|
||||
{
|
||||
Console.Write($", internalPrometheusEndpoint = http://localhost:{options.PrometheusInternalMetricsPort}/metrics/");
|
||||
}
|
||||
|
||||
this.WriteRunInformationToConsole();
|
||||
|
||||
Console.WriteLine("), press <Esc> to stop, press <Spacebar> to toggle statistics in the console...");
|
||||
Console.WriteLine(this.output);
|
||||
|
||||
var outputCursorTop = Console.CursorTop - 1;
|
||||
|
||||
var bOutput = true;
|
||||
var watch = new Stopwatch();
|
||||
while (true)
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
var key = Console.ReadKey(true).Key;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case ConsoleKey.Enter:
|
||||
Console.WriteLine(string.Format("{0} {1}", DateTime.UtcNow.ToString("O"), this.output));
|
||||
break;
|
||||
case ConsoleKey.Escape:
|
||||
this.bContinue = false;
|
||||
return;
|
||||
case ConsoleKey.Spacebar:
|
||||
bOutput = !bOutput;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bOutput)
|
||||
{
|
||||
var tempCursorLeft = Console.CursorLeft;
|
||||
var tempCursorTop = Console.CursorTop;
|
||||
Console.SetCursorPosition(0, outputCursorTop);
|
||||
Console.WriteLine(this.output.PadRight(Console.BufferWidth));
|
||||
Console.SetCursorPosition(tempCursorLeft, tempCursorTop);
|
||||
}
|
||||
|
||||
var cntLoopsOld = (ulong)statistics.Sum();
|
||||
var cntCpuCyclesOld = StressTestNativeMethods.GetCpuCycles();
|
||||
|
||||
watch.Restart();
|
||||
Thread.Sleep(200);
|
||||
watch.Stop();
|
||||
|
||||
cntLoopsTotal = (ulong)statistics.Sum();
|
||||
var cntCpuCyclesNew = StressTestNativeMethods.GetCpuCycles();
|
||||
|
||||
var nLoops = cntLoopsTotal - cntLoopsOld;
|
||||
var nCpuCycles = cntCpuCyclesNew - cntCpuCyclesOld;
|
||||
|
||||
dLoopsPerSecond = (double)nLoops / ((double)watch.ElapsedMilliseconds / 1000.0);
|
||||
dCpuCyclesPerLoop = nLoops == 0 ? 0 : nCpuCycles / nLoops;
|
||||
|
||||
var totalElapsedTime = watchForTotal.Elapsed;
|
||||
|
||||
if (duration.HasValue)
|
||||
{
|
||||
this.output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}, RemainingTime (Seconds): {(duration.Value - totalElapsedTime).TotalSeconds:n0}";
|
||||
if (totalElapsedTime > duration)
|
||||
{
|
||||
this.bContinue = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}, RunningTime (Seconds): {totalElapsedTime.TotalSeconds:n0}";
|
||||
}
|
||||
|
||||
Console.Title = this.output;
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
Parallel.For(0, options.Concurrency, (i) =>
|
||||
{
|
||||
ref var count = ref statistics[i];
|
||||
|
||||
while (this.bContinue)
|
||||
{
|
||||
this.RunWorkItemInParallel();
|
||||
count++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
watchForTotal.Stop();
|
||||
cntLoopsTotal = (ulong)statistics.Sum();
|
||||
var totalLoopsPerSecond = (double)cntLoopsTotal / ((double)watchForTotal.ElapsedMilliseconds / 1000.0);
|
||||
var cntCpuCyclesTotal = StressTestNativeMethods.GetCpuCycles();
|
||||
var cpuCyclesPerLoopTotal = cntLoopsTotal == 0 ? 0 : cntCpuCyclesTotal / cntLoopsTotal;
|
||||
Console.WriteLine("Stopping the stress test...");
|
||||
Console.WriteLine($"* Total Running 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}");
|
||||
#if !NETFRAMEWORK
|
||||
Console.WriteLine($"* GC Total Allocated Bytes: {GC.GetTotalAllocatedBytes()}");
|
||||
#endif
|
||||
}
|
||||
|
||||
protected virtual void WriteRunInformationToConsole()
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract void RunWorkItemInParallel();
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using CommandLine;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public static class StressTestFactory
|
||||
{
|
||||
public static int RunSynchronously<TStressTest>(string[] commandLineArguments)
|
||||
where TStressTest : StressTest<StressTestOptions>
|
||||
{
|
||||
return RunSynchronously<TStressTest, StressTestOptions>(commandLineArguments);
|
||||
}
|
||||
|
||||
public static int RunSynchronously<TStressTest, TStressTestOptions>(string[] commandLineArguments)
|
||||
where TStressTest : StressTest<TStressTestOptions>
|
||||
where TStressTestOptions : StressTestOptions
|
||||
{
|
||||
return Parser.Default.ParseArguments<TStressTestOptions>(commandLineArguments)
|
||||
.MapResult(
|
||||
CreateStressTestAndRunSynchronously,
|
||||
_ => 1);
|
||||
|
||||
static int CreateStressTestAndRunSynchronously(TStressTestOptions options)
|
||||
{
|
||||
using var stressTest = (TStressTest)Activator.CreateInstance(typeof(TStressTest), options)!;
|
||||
|
||||
stressTest.RunSynchronously();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
internal static class StressTestNativeMethods
|
||||
{
|
||||
public static ulong GetCpuCycles()
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!QueryProcessCycleTime((IntPtr)(-1), out var cycles))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cycles;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool QueryProcessCycleTime(IntPtr hProcess, out ulong cycles);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using CommandLine;
|
||||
|
||||
namespace OpenTelemetry.Tests.Stress;
|
||||
|
||||
public class StressTestOptions
|
||||
{
|
||||
[Option('c', "concurrency", HelpText = "The concurrency (maximum degree of parallelism) for the stress test. Default value: Environment.ProcessorCount.", Required = false)]
|
||||
public int Concurrency { get; set; }
|
||||
|
||||
[Option('p', "internal_port", HelpText = "The Prometheus http listener port where Prometheus will be exposed for retrieving internal metrics while the stress test is running. Set to '0' to disable. Default value: 9464.", Required = false)]
|
||||
public int PrometheusInternalMetricsPort { get; set; } = 9464;
|
||||
|
||||
[Option('d', "duration", HelpText = "The duration for the stress test to run in seconds. If set to '0' or a negative value the stress test will run until canceled. Default value: 0.", Required = false)]
|
||||
public int DurationSeconds { get; set; }
|
||||
}
|
||||
Loading…
Reference in New Issue