Skip to content

Commit 2f14c24

Browse files
jcin193reyangCodeBlanchvishweshbankwar
authored
[prometheus] Fix collection output buffer management when its resized (#5676)
Co-authored-by: Reiley Yang <reyang@microsoft.com> Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com> Co-authored-by: Vishwesh Bankwar <vishweshbankwar@users.noreply.github.com>
1 parent 4af3df9 commit 2f14c24

File tree

5 files changed

+96
-16
lines changed

5 files changed

+96
-16
lines changed

src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Fixed a bug which lead to empty responses when the internal buffer is resized
6+
processing a collection request
7+
([#5676](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5676))
8+
59
## 1.9.0-beta.1
610

711
Released 2024-Jun-14

src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Fixed a bug which lead to empty responses when the internal buffer is resized
6+
processing a collection request
7+
([#5676](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5676))
8+
59
## 1.9.0-beta.1
610

711
Released 2024-Jun-14

src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,13 @@ private bool ExecuteCollect(bool openMetricsRequested)
198198
private ExportResult OnCollect(Batch<Metric> metrics)
199199
{
200200
var cursor = 0;
201-
var buffer = this.exporter.OpenMetricsRequested ? this.openMetricsBuffer : this.plainTextBuffer;
201+
ref byte[] buffer = ref (this.exporter.OpenMetricsRequested ? ref this.openMetricsBuffer : ref this.plainTextBuffer);
202202

203203
try
204204
{
205205
if (this.exporter.OpenMetricsRequested)
206206
{
207-
cursor = this.WriteTargetInfo();
207+
cursor = this.WriteTargetInfo(ref buffer);
208208

209209
this.scopes.Clear();
210210

@@ -291,11 +291,11 @@ private ExportResult OnCollect(Batch<Metric> metrics)
291291

292292
if (this.exporter.OpenMetricsRequested)
293293
{
294-
this.previousOpenMetricsDataView = new ArraySegment<byte>(this.openMetricsBuffer, 0, cursor);
294+
this.previousOpenMetricsDataView = new ArraySegment<byte>(buffer, 0, cursor);
295295
}
296296
else
297297
{
298-
this.previousPlainTextDataView = new ArraySegment<byte>(this.plainTextBuffer, 0, cursor);
298+
this.previousPlainTextDataView = new ArraySegment<byte>(buffer, 0, cursor);
299299
}
300300

301301
return ExportResult.Success;
@@ -315,21 +315,21 @@ private ExportResult OnCollect(Batch<Metric> metrics)
315315
}
316316
}
317317

318-
private int WriteTargetInfo()
318+
private int WriteTargetInfo(ref byte[] buffer)
319319
{
320320
if (this.targetInfoBufferLength < 0)
321321
{
322322
while (true)
323323
{
324324
try
325325
{
326-
this.targetInfoBufferLength = PrometheusSerializer.WriteTargetInfo(this.openMetricsBuffer, 0, this.exporter.Resource);
326+
this.targetInfoBufferLength = PrometheusSerializer.WriteTargetInfo(buffer, 0, this.exporter.Resource);
327327

328328
break;
329329
}
330330
catch (IndexOutOfRangeException)
331331
{
332-
if (!this.IncreaseBufferSize(ref this.openMetricsBuffer))
332+
if (!this.IncreaseBufferSize(ref buffer))
333333
{
334334
throw;
335335
}

test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,30 @@ public async Task PrometheusExporterMiddlewareIntegration_CanServeOpenMetricsAnd
288288
await host.StopAsync();
289289
}
290290

291+
[Fact]
292+
public async Task PrometheusExporterMiddlewareIntegration_TestBufferSizeIncrease_With_LotOfMetrics()
293+
{
294+
using var host = await StartTestHostAsync(
295+
app => app.UseOpenTelemetryPrometheusScrapingEndpoint());
296+
297+
using var meter = new Meter(MeterName, MeterVersion);
298+
299+
for (var x = 0; x < 1000; x++)
300+
{
301+
var counter = meter.CreateCounter<double>("counter_double_" + x, unit: "By");
302+
counter.Add(1);
303+
}
304+
305+
using var client = host.GetTestClient();
306+
307+
using var response = await client.GetAsync("/metrics");
308+
var text = await response.Content.ReadAsStringAsync();
309+
310+
Assert.NotEmpty(text);
311+
312+
await host.StopAsync();
313+
}
314+
291315
private static async Task RunPrometheusExporterMiddlewareIntegrationTest(
292316
string path,
293317
Action<IApplicationBuilder> configure,

test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,45 @@ public void PrometheusHttpListenerThrowsOnStart()
144144
listener?.Dispose();
145145
}
146146

147+
[Theory]
148+
[InlineData("application/openmetrics-text")]
149+
[InlineData("")]
150+
public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease_With_LargePayload(string acceptHeader)
151+
{
152+
using var meter = new Meter(MeterName, MeterVersion);
153+
154+
var attributes = new List<KeyValuePair<string, object>>();
155+
var oneKb = new string('A', 1024);
156+
for (var x = 0; x < 8500; x++)
157+
{
158+
attributes.Add(new KeyValuePair<string, object>(x.ToString(), oneKb));
159+
}
160+
161+
var provider = BuildMeterProvider(meter, attributes, out var address);
162+
163+
for (var x = 0; x < 1000; x++)
164+
{
165+
var counter = meter.CreateCounter<double>("counter_double_" + x, unit: "By");
166+
counter.Add(1);
167+
}
168+
169+
using HttpClient client = new HttpClient();
170+
171+
if (!string.IsNullOrEmpty(acceptHeader))
172+
{
173+
client.DefaultRequestHeaders.Add("Accept", acceptHeader);
174+
}
175+
176+
using var response = await client.GetAsync($"{address}metrics");
177+
178+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
179+
var content = await response.Content.ReadAsStringAsync();
180+
Assert.Contains("counter_double_999", content);
181+
Assert.DoesNotContain('\0', content);
182+
183+
provider.Dispose();
184+
}
185+
147186
private static void TestPrometheusHttpListenerUriPrefixOptions(string[] uriPrefixes)
148187
{
149188
using var exporter = new PrometheusExporter(new());
@@ -155,31 +194,27 @@ private static void TestPrometheusHttpListenerUriPrefixOptions(string[] uriPrefi
155194
});
156195
}
157196

158-
private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text")
197+
private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable<KeyValuePair<string, object>> attributes, out string address)
159198
{
160-
var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text");
161-
162199
Random random = new Random();
163200
int retryAttempts = 5;
164201
int port = 0;
165-
string address = null;
166-
202+
string generatedAddress = null;
167203
MeterProvider provider = null;
168-
using var meter = new Meter(MeterName, MeterVersion);
169204

170205
while (retryAttempts-- != 0)
171206
{
172207
port = random.Next(2000, 5000);
173-
address = $"http://localhost:{port}/";
208+
generatedAddress = $"http://localhost:{port}/";
174209

175210
try
176211
{
177212
provider = Sdk.CreateMeterProviderBuilder()
178213
.AddMeter(meter.Name)
179-
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1"))
214+
.ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1").AddAttributes(attributes))
180215
.AddPrometheusHttpListener(options =>
181216
{
182-
options.UriPrefixes = new string[] { address };
217+
options.UriPrefixes = new string[] { generatedAddress };
183218
})
184219
.Build();
185220

@@ -191,11 +226,24 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri
191226
}
192227
}
193228

229+
address = generatedAddress;
230+
194231
if (provider == null)
195232
{
196233
throw new InvalidOperationException("HttpListener could not be started");
197234
}
198235

236+
return provider;
237+
}
238+
239+
private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text")
240+
{
241+
var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text");
242+
243+
using var meter = new Meter(MeterName, MeterVersion);
244+
245+
var provider = BuildMeterProvider(meter, [], out var address);
246+
199247
var tags = new KeyValuePair<string, object>[]
200248
{
201249
new KeyValuePair<string, object>("key1", "value1"),

0 commit comments

Comments
 (0)