Skip to content

Commit 44a87f2

Browse files
hananielTimHess
andauthored
Make Steeltoe Metrics extensions play nice with Otel (#848)
* Adds checks and warnings when extension methods are used incorrectly with Opentelemetry * Fix Threaddump extension methods * Apply suggestions from code review Co-authored-by: Tim Hess <thess@vmware.com> * Easy configuration for actuators & Otel. Fix thread dump extension for non-windows * Fix failing testS * CR Fixes Co-authored-by: Tim Hess <thess@vmware.com>
1 parent bc62967 commit 44a87f2

File tree

11 files changed

+251
-73
lines changed

11 files changed

+251
-73
lines changed

src/Management/src/EndpointBase/Metrics/MetricsEndpoint.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,21 @@ protected internal MetricsResponse GetMetric(MetricsRequest request, List<Metric
101101

102102
protected internal void GetMetricsCollection(out MetricsCollection<List<MetricSample>> metricSamples, out MetricsCollection<List<MetricTag>> availTags)
103103
{
104-
var collectionResponse = (SteeltoeCollectionResponse)_exporter.CollectionManager.EnterCollect().Result;
105-
metricSamples = collectionResponse.MetricSamples;
106-
availTags = collectionResponse.AvailableTags;
104+
var response = _exporter.CollectionManager.EnterCollect().Result;
105+
106+
if (response is SteeltoeCollectionResponse collectionResponse)
107+
{
108+
metricSamples = collectionResponse.MetricSamples;
109+
availTags = collectionResponse.AvailableTags;
110+
return;
111+
}
112+
else
113+
{
114+
_logger?.LogWarning("Please ensure OpenTelemetry is configured via Steeltoe extension methods.");
115+
}
116+
117+
metricSamples = new MetricsCollection<List<MetricSample>>();
118+
availTags = new MetricsCollection<List<MetricTag>>();
107119

108120
// TODO: update the response header with actual updatetime
109121
}

src/Management/src/EndpointBase/Metrics/Prometheus/PrometheusScraperEndpoint.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,28 @@ public override string Invoke()
2828
var result = string.Empty;
2929
try
3030
{
31-
var collectionResponse = (PrometheusCollectionResponse)_exporter.CollectionManager.EnterCollect().Result;
32-
try
31+
var response = _exporter.CollectionManager.EnterCollect().Result;
32+
if (response is PrometheusCollectionResponse collectionResponse)
3333
{
34-
if (collectionResponse.View.Count > 0)
34+
try
3535
{
36-
result = Encoding.UTF8.GetString(collectionResponse.View.Array, 0, collectionResponse.View.Count);
36+
if (collectionResponse.View.Count > 0)
37+
{
38+
result = Encoding.UTF8.GetString(collectionResponse.View.Array, 0, collectionResponse.View.Count);
39+
}
40+
else
41+
{
42+
throw new InvalidOperationException("Collection failure.");
43+
}
3744
}
38-
else
45+
finally
3946
{
40-
throw new InvalidOperationException("Collection failure.");
47+
_exporter.CollectionManager.ExitCollect();
4148
}
4249
}
43-
finally
50+
else
4451
{
45-
_exporter.CollectionManager.ExitCollect();
52+
_logger?.LogWarning("Please ensure OpenTelemetry is configured via Steeltoe extension methods.");
4653
}
4754
}
4855
catch (Exception ex)

src/Management/src/EndpointBase/Metrics/ServiceCollectionExtensions.cs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Microsoft.Extensions.Configuration;
66
using Microsoft.Extensions.DependencyInjection.Extensions;
77
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
using OpenTelemetry;
810
using OpenTelemetry.Metrics;
911
using Steeltoe.Common.Diagnostics;
1012
using Steeltoe.Management;
@@ -15,6 +17,8 @@
1517
using Steeltoe.Management.OpenTelemetry.Metrics;
1618
using System;
1719
using System.Diagnostics;
20+
using System.Diagnostics.Metrics;
21+
using System.Linq;
1822

1923
namespace Microsoft.Extensions.DependencyInjection
2024
{
@@ -54,8 +58,8 @@ public static IServiceCollection AddMetricsActuatorServices(this IServiceCollect
5458
var exporterOptions = new PullmetricsExporterOptions() { ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds };
5559
return new SteeltoeExporter(exporterOptions);
5660
}));
57-
5861
services.AddOpenTelemetryMetricsForSteeltoe();
62+
5963
return services;
6064
}
6165

@@ -87,31 +91,69 @@ public static IServiceCollection AddPrometheusActuatorServices(this IServiceColl
8791
var exporterOptions = new PullmetricsExporterOptions() { ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds };
8892
return new SteeltoePrometheusExporter(exporterOptions);
8993
}));
94+
9095
services.AddOpenTelemetryMetricsForSteeltoe();
9196

9297
return services;
9398
}
9499

95-
public static IServiceCollection AddOpenTelemetryMetricsForSteeltoe(this IServiceCollection services, string name = null, string version = null)
100+
/// <summary>
101+
/// Helper method to configure opentelemetry metrics. Do not use in conjuction with Extension methods provided by OpenTelemetry.
102+
/// </summary>
103+
/// <param name="services">Reference to the service collection</param>
104+
/// <param name="configure">The Action to configure OpenTelemetry</param>
105+
/// <param name="name">Instrumentation Name </param>
106+
/// <param name="version">Instrumentation Version</param>
107+
/// <returns>A reference to the service collection </returns>
108+
public static IServiceCollection AddOpenTelemetryMetricsForSteeltoe(this IServiceCollection services, Action<IServiceProvider, MeterProviderBuilder> configure = null, string name = null, string version = null)
109+
{
110+
if (services.Any(sd => sd.ServiceType == typeof(MeterProvider)))
111+
{
112+
if (!services.Any(sd => sd.ImplementationInstance?.ToString() == "{ ConfiguredSteeltoeMetrics = True }"))
113+
{
114+
Console.WriteLine("Warning!! Make sure one of the extension methods that calls ConfigureSteeltoeMetrics is used to correctly configure metrics using OpenTelemetry for Steeltoe.");
115+
}
116+
117+
return services; // Already Configured, get out of here
118+
}
119+
120+
services.AddSingleton(new { ConfiguredSteeltoeMetrics = true });
121+
return services.AddOpenTelemetryMetrics(builder => builder.ConfigureSteeltoeMetrics());
122+
}
123+
124+
/// <summary>
125+
/// Configures the <see cref="MeterProviderBuilder"></see> as an underlying Metrics processor and exporter for Steeltoe in actuators and exporters. />
126+
/// </summary>
127+
/// <param name="builder">MeterProviderBuilder </param>
128+
/// <param name="configure"> Configuration callback</param>
129+
/// <param name="name">Instrumentation Name</param>
130+
/// <param name="version">Instrumentation Version</param>
131+
/// <returns>Configured MeterProviderBuilder</returns>
132+
public static MeterProviderBuilder ConfigureSteeltoeMetrics(this MeterProviderBuilder builder, Action<IServiceProvider, MeterProviderBuilder> configure = null, string name = null, string version = null)
96133
{
97-
return services.AddOpenTelemetryMetrics(builder =>
134+
if (configure != null)
98135
{
99-
builder.Configure((provider, deferredBuilder) =>
136+
builder.Configure(configure);
137+
}
138+
139+
builder.Configure((provider, deferredBuilder) =>
140+
{
141+
var views = provider.GetService<IViewRegistry>();
142+
var exporters = provider.GetServices(typeof(IMetricsExporter)) as System.Collections.Generic.IEnumerable<IMetricsExporter>;
143+
144+
deferredBuilder
145+
.AddMeter(name ?? OpenTelemetryMetrics.InstrumentationName, version ?? OpenTelemetryMetrics.InstrumentationVersion)
146+
.AddRegisteredViews(views)
147+
.AddExporters(exporters);
148+
149+
var wavefrontExporter = provider.GetService<WavefrontMetricsExporter>(); // Not an IMetricsExporter
150+
151+
if (wavefrontExporter != null)
100152
{
101-
var views = provider.GetService<IViewRegistry>();
102-
var exporters = provider.GetServices(typeof(IMetricsExporter)) as System.Collections.Generic.IEnumerable<IMetricsExporter>;
103-
deferredBuilder
104-
.AddMeter(name ?? OpenTelemetryMetrics.InstrumentationName, version ?? OpenTelemetryMetrics.InstrumentationVersion)
105-
.AddRegisteredViews(views)
106-
.AddExporters(exporters);
107-
108-
var wavefrontExporter = provider.GetService<WavefrontMetricsExporter>(); // Not an IMetricsExporter
109-
if (wavefrontExporter != null)
110-
{
111-
deferredBuilder.AddWavefrontExporter(wavefrontExporter);
112-
}
113-
});
153+
deferredBuilder.AddWavefrontExporter(wavefrontExporter);
154+
}
114155
});
156+
return builder;
115157
}
116158
}
117159
}

src/Management/src/EndpointCore/ActuatorServiceCollectionExtensions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,8 @@ public static IServiceCollection AddAllActuators(this IServiceCollection service
5757
}
5858

5959
services.AddHypermediaActuator(config);
60-
if (Platform.IsWindows)
61-
{
62-
services.AddThreadDumpActuator(config, version);
63-
}
60+
61+
services.AddThreadDumpActuator(config, version);
6462

6563
services.AddHeapDumpActuator(config);
6664

src/Management/src/OpenTelemetryBase/Exporters/Prometheus/PrometheusSerializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Steeltoe.Management.OpenTelemetry.Exporters.Prometheus
1313
{
1414
/// <summary>
1515
/// Basic PrometheusSerializer which has no OpenTelemetry dependency.
16-
/// Copied from Opentelemetry.Net project
16+
/// Copied from OpenTelemetry.Net project
1717
/// </summary>
1818
internal static partial class PrometheusSerializer
1919
{

src/Management/src/OpenTelemetryBase/Exporters/PullmetricsCollectionManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Steeltoe.Management.OpenTelemetry.Exporters
1313
{
1414
#pragma warning disable SX1309 // Field names should begin with underscore
1515

16-
// Adapted from Opentelemetry.Net project
16+
// Adapted from OpenTelemetry.Net project
1717
internal sealed partial class PullmetricsCollectionManager
1818
{
1919
private readonly IMetricsExporter exporter;
@@ -165,7 +165,7 @@ private bool ExecuteCollect()
165165
this.exporter.OnExport = this.onCollectRef;
166166
var result = this.exporter.Collect?.Invoke(Timeout.Infinite);
167167
this.exporter.OnExport = null;
168-
return result.HasValue ? result.Value : false;
168+
return result.GetValueOrDefault();
169169
}
170170

171171
private ExportResult OnCollect(Batch<Metric> metrics)

src/Management/src/OpenTelemetryBase/Exporters/Wavefront/WavefrontExporterOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ public WavefrontExporterOptions(IConfiguration config)
2424

2525
public string ApiToken { get; set; }
2626

27-
public int Step { get; set; } = 30000; // milliseconds
27+
public int Step { get; set; } = 30_000; // milliseconds
2828

29-
public int BatchSize { get; set; } = 10000;
29+
public int BatchSize { get; set; } = 10_000;
3030

31-
public int MaxQueueSize { get; set; } = 1000;
31+
public int MaxQueueSize { get; set; } = 500_000;
3232

3333
public WavefrontApplicationOptions ApplicationOptions { get; }
3434

src/Management/test/CloudFoundryCore.Test/CloudFoundryHostBuilderExtensionsTest.cs

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,7 @@ public void AddCloudFoundryActuators_IWebHostBuilder()
4545

4646
Assert.Contains(managementOptions, t => t.GetType() == typeof(CloudFoundryManagementOptions));
4747

48-
if (Platform.IsWindows)
49-
{
50-
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
51-
}
52-
else
53-
{
54-
Assert.Empty(host.Services.GetServices<ThreadDumpEndpoint_v2>());
55-
}
48+
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
5649

5750
Assert.NotNull(filters);
5851
Assert.Single(filters.OfType<CloudFoundryActuatorsStartupFilter>());
@@ -72,15 +65,7 @@ public void AddCloudFoundryActuators_IWebHostBuilder_Serilog()
7265
var filters = host.Services.GetServices<IStartupFilter>();
7366

7467
Assert.Contains(managementOptions, t => t.GetType() == typeof(CloudFoundryManagementOptions));
75-
76-
if (Platform.IsWindows)
77-
{
78-
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
79-
}
80-
else
81-
{
82-
Assert.Empty(host.Services.GetServices<ThreadDumpEndpoint_v2>());
83-
}
68+
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
8469

8570
Assert.Single(host.Services.GetServices<HeapDumpEndpoint>());
8671

@@ -99,15 +84,7 @@ public void AddCloudFoundryActuators_IHostBuilder()
9984
var filter = host.Services.GetServices<IStartupFilter>().FirstOrDefault();
10085

10186
Assert.Contains(managementOptions, t => t.GetType() == typeof(CloudFoundryManagementOptions));
102-
103-
if (Platform.IsWindows)
104-
{
105-
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint>());
106-
}
107-
else
108-
{
109-
Assert.Empty(host.Services.GetServices<ThreadDumpEndpoint>());
110-
}
87+
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint>());
11188

11289
Assert.Single(host.Services.GetServices<HeapDumpEndpoint>());
11390

@@ -164,15 +141,7 @@ public void AddCloudFoundryActuators_IHostBuilder_Serilog()
164141
var filters = host.Services.GetServices<IStartupFilter>();
165142

166143
Assert.Contains(managementOptions, t => t.GetType() == typeof(CloudFoundryManagementOptions));
167-
168-
if (Platform.IsWindows)
169-
{
170-
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
171-
}
172-
else
173-
{
174-
Assert.Empty(host.Services.GetServices<ThreadDumpEndpoint_v2>());
175-
}
144+
Assert.Single(host.Services.GetServices<ThreadDumpEndpoint_v2>());
176145

177146
Assert.Single(host.Services.GetServices<HeapDumpEndpoint>());
178147

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Text;
9+
10+
namespace Steeltoe.Management.Endpoint.Test
11+
{
12+
internal class ConsoleOutputBorrower : IDisposable
13+
{
14+
private readonly StringWriter _borrowedOutput;
15+
private readonly TextWriter _originalOutput;
16+
17+
public ConsoleOutputBorrower()
18+
{
19+
_borrowedOutput = new StringWriter();
20+
_originalOutput = Console.Out;
21+
Console.SetOut(_borrowedOutput);
22+
}
23+
24+
public override string ToString()
25+
{
26+
return _borrowedOutput.ToString();
27+
}
28+
29+
public void Dispose()
30+
{
31+
Console.SetOut(_originalOutput);
32+
_borrowedOutput.Dispose();
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)