OTLP support for exporting exponential histograms (#4337)
This commit is contained in:
parent
87a33138ed
commit
410532ae49
|
|
@ -14,6 +14,7 @@
|
|||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
using Examples.AspNetCore;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore;
|
||||
|
|
@ -33,6 +34,9 @@ var metricsExporter = appBuilder.Configuration.GetValue<string>("UseMetricsExpor
|
|||
// Note: Switch between Console/OTLP by setting UseLogExporter in appsettings.json.
|
||||
var logExporter = appBuilder.Configuration.GetValue<string>("UseLogExporter").ToLowerInvariant();
|
||||
|
||||
// Note: Switch between Explicit/Exponential by setting HistogramAggregation in appsettings.json
|
||||
var histogramAggregation = appBuilder.Configuration.GetValue<string>("HistogramAggregation").ToLowerInvariant();
|
||||
|
||||
// Build a resource configuration action to set service information.
|
||||
Action<ResourceBuilder> configureResource = r => r.AddService(
|
||||
serviceName: appBuilder.Configuration.GetValue<string>("ServiceName"),
|
||||
|
|
@ -111,6 +115,22 @@ appBuilder.Services.AddOpenTelemetry()
|
|||
.AddHttpClientInstrumentation()
|
||||
.AddAspNetCoreInstrumentation();
|
||||
|
||||
switch (histogramAggregation)
|
||||
{
|
||||
case "exponential":
|
||||
builder.AddView(instrument =>
|
||||
{
|
||||
return instrument.GetType().GetGenericTypeDefinition() == typeof(Histogram<>)
|
||||
? new Base2ExponentialBucketHistogramConfiguration()
|
||||
: null;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// Explicit bounds histogram is the default.
|
||||
// No additional configuration necessary.
|
||||
break;
|
||||
}
|
||||
|
||||
switch (metricsExporter)
|
||||
{
|
||||
case "prometheus":
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"UseTracingExporter": "console",
|
||||
"UseMetricsExporter": "console",
|
||||
"UseLogExporter": "console",
|
||||
"HistogramAggregation": "explicit",
|
||||
"Jaeger": {
|
||||
"AgentHost": "localhost",
|
||||
"AgentPort": 6831,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ namespace OpenTelemetry.Exporter
|
|||
|
||||
var metricType = metric.MetricType;
|
||||
|
||||
if (metricType.IsHistogram())
|
||||
if (metricType == MetricType.Histogram || metricType == MetricType.ExponentialHistogram)
|
||||
{
|
||||
var bucketsBuilder = new StringBuilder();
|
||||
var sum = metricPoint.GetHistogramSum();
|
||||
|
|
@ -105,41 +105,49 @@ namespace OpenTelemetry.Exporter
|
|||
|
||||
bucketsBuilder.AppendLine();
|
||||
|
||||
bool isFirstIteration = true;
|
||||
double previousExplicitBound = default;
|
||||
foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets())
|
||||
if (metricType == MetricType.Histogram)
|
||||
{
|
||||
if (isFirstIteration)
|
||||
bool isFirstIteration = true;
|
||||
double previousExplicitBound = default;
|
||||
foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets())
|
||||
{
|
||||
bucketsBuilder.Append("(-Infinity,");
|
||||
bucketsBuilder.Append(histogramMeasurement.ExplicitBound);
|
||||
bucketsBuilder.Append(']');
|
||||
bucketsBuilder.Append(':');
|
||||
bucketsBuilder.Append(histogramMeasurement.BucketCount);
|
||||
previousExplicitBound = histogramMeasurement.ExplicitBound;
|
||||
isFirstIteration = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketsBuilder.Append('(');
|
||||
bucketsBuilder.Append(previousExplicitBound);
|
||||
bucketsBuilder.Append(',');
|
||||
if (histogramMeasurement.ExplicitBound != double.PositiveInfinity)
|
||||
if (isFirstIteration)
|
||||
{
|
||||
bucketsBuilder.Append("(-Infinity,");
|
||||
bucketsBuilder.Append(histogramMeasurement.ExplicitBound);
|
||||
bucketsBuilder.Append(']');
|
||||
bucketsBuilder.Append(':');
|
||||
bucketsBuilder.Append(histogramMeasurement.BucketCount);
|
||||
previousExplicitBound = histogramMeasurement.ExplicitBound;
|
||||
isFirstIteration = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketsBuilder.Append("+Infinity");
|
||||
bucketsBuilder.Append('(');
|
||||
bucketsBuilder.Append(previousExplicitBound);
|
||||
bucketsBuilder.Append(',');
|
||||
if (histogramMeasurement.ExplicitBound != double.PositiveInfinity)
|
||||
{
|
||||
bucketsBuilder.Append(histogramMeasurement.ExplicitBound);
|
||||
previousExplicitBound = histogramMeasurement.ExplicitBound;
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketsBuilder.Append("+Infinity");
|
||||
}
|
||||
|
||||
bucketsBuilder.Append(']');
|
||||
bucketsBuilder.Append(':');
|
||||
bucketsBuilder.Append(histogramMeasurement.BucketCount);
|
||||
}
|
||||
|
||||
bucketsBuilder.Append(']');
|
||||
bucketsBuilder.Append(':');
|
||||
bucketsBuilder.Append(histogramMeasurement.BucketCount);
|
||||
bucketsBuilder.AppendLine();
|
||||
}
|
||||
|
||||
bucketsBuilder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Consider how/if to display buckets for exponential histograms.
|
||||
bucketsBuilder.AppendLine("Buckets are not displayed for exponential histograms.");
|
||||
}
|
||||
|
||||
valueDisplay = bucketsBuilder.ToString();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
* Add support for exporting histograms aggregated using the
|
||||
[Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation).
|
||||
([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337))
|
||||
|
||||
* Added support to set `TraceState` when converting the
|
||||
System.Diagnostics.Activity object to its corresponding
|
||||
OpenTelemetry.Proto.Trace.V1.Span object.
|
||||
|
|
|
|||
|
|
@ -307,6 +307,57 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
|
|||
otlpMetric.Histogram = histogram;
|
||||
break;
|
||||
}
|
||||
|
||||
case MetricType.ExponentialHistogram:
|
||||
{
|
||||
var histogram = new OtlpMetrics.ExponentialHistogram
|
||||
{
|
||||
AggregationTemporality = temporality,
|
||||
};
|
||||
|
||||
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
|
||||
{
|
||||
var dataPoint = new OtlpMetrics.ExponentialHistogramDataPoint
|
||||
{
|
||||
StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(),
|
||||
TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(),
|
||||
};
|
||||
|
||||
AddAttributes(metricPoint.Tags, dataPoint.Attributes);
|
||||
dataPoint.Count = (ulong)metricPoint.GetHistogramCount();
|
||||
dataPoint.Sum = metricPoint.GetHistogramSum();
|
||||
|
||||
if (metricPoint.TryGetHistogramMinMaxValues(out double min, out double max))
|
||||
{
|
||||
dataPoint.Min = min;
|
||||
dataPoint.Max = max;
|
||||
}
|
||||
|
||||
var exponentialHistogramData = metricPoint.GetExponentialHistogramData();
|
||||
dataPoint.Scale = exponentialHistogramData.Scale;
|
||||
|
||||
dataPoint.Positive = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets();
|
||||
dataPoint.Positive.Offset = exponentialHistogramData.PositiveBuckets.Offset;
|
||||
foreach (var bucketCount in exponentialHistogramData.PositiveBuckets)
|
||||
{
|
||||
dataPoint.Positive.BucketCounts.Add((ulong)bucketCount);
|
||||
}
|
||||
|
||||
dataPoint.Negative = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets();
|
||||
dataPoint.Negative.Offset = exponentialHistogramData.NegativeBuckets.Offset;
|
||||
foreach (var bucketCount in exponentialHistogramData.NegativeBuckets)
|
||||
{
|
||||
dataPoint.Negative.BucketCounts.Add((ulong)bucketCount);
|
||||
}
|
||||
|
||||
// TODO: exemplars.
|
||||
|
||||
histogram.DataPoints.Add(dataPoint);
|
||||
}
|
||||
|
||||
otlpMetric.ExponentialHistogram = histogram;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return otlpMetric;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ namespace OpenTelemetry.Exporter.Prometheus
|
|||
|
||||
public static int WriteMetric(byte[] buffer, int cursor, Metric metric)
|
||||
{
|
||||
if (metric.MetricType == MetricType.ExponentialHistogram)
|
||||
{
|
||||
// Exponential histograms are not yet support by Prometheus.
|
||||
// They are ignored for now.
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int metricType = (int)metric.MetricType >> 4;
|
||||
cursor = WriteTypeMetadata(buffer, cursor, metric.Name, metric.Unit, MetricTypes[metricType]);
|
||||
cursor = WriteUnitMetadata(buffer, cursor, metric.Name, metric.Unit);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter
|
|||
OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void
|
||||
OpenTelemetry.Metrics.Exemplar
|
||||
OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double
|
||||
OpenTelemetry.Metrics.Exemplar.Exemplar() -> void
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter
|
|||
OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void
|
||||
OpenTelemetry.Metrics.Exemplar
|
||||
OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double
|
||||
OpenTelemetry.Metrics.Exemplar.Exemplar() -> void
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter
|
|||
OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void
|
||||
OpenTelemetry.Metrics.Exemplar
|
||||
OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double
|
||||
OpenTelemetry.Metrics.Exemplar.Exemplar() -> void
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter
|
|||
OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter
|
||||
OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int
|
||||
OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void
|
||||
OpenTelemetry.Metrics.Exemplar
|
||||
OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double
|
||||
OpenTelemetry.Metrics.Exemplar.Exemplar() -> void
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
* Add support for configuring the
|
||||
[Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation)
|
||||
using the `AddView` API. This aggregation is supported by OTLP but not yet by
|
||||
Prometheus.
|
||||
([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337))
|
||||
|
||||
* Implementation of `SuppressInstrumentationScope` changed to improve
|
||||
performance.
|
||||
([#4304](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4304))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace OpenTelemetry.Metrics;
|
|||
/// <summary>
|
||||
/// Stores configuration for a histogram metric stream with base-2 exponential bucket boundaries.
|
||||
/// </summary>
|
||||
internal sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration
|
||||
public sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration
|
||||
{
|
||||
private int maxSize = Metric.DefaultExponentialHistogramMaxBuckets;
|
||||
private int maxScale = Metric.DefaultExponentialHistogramMaxScale;
|
||||
|
|
|
|||
|
|
@ -469,6 +469,103 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests
|
|||
Assert.Empty(dataPoint.Exemplars);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)]
|
||||
[InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)]
|
||||
[InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta)]
|
||||
[InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)]
|
||||
[InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, "key1", "value1", "key2", 123)]
|
||||
public void TestExponentialHistogramToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, params object[] keysValues)
|
||||
{
|
||||
var metrics = new List<Metric>();
|
||||
|
||||
using var meter = new Meter(Utils.GetCurrentMethodName());
|
||||
using var provider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter(meter.Name)
|
||||
.AddInMemoryExporter(metrics, metricReaderOptions =>
|
||||
{
|
||||
metricReaderOptions.TemporalityPreference = aggregationTemporality;
|
||||
})
|
||||
.AddView(instrument =>
|
||||
{
|
||||
return new Base2ExponentialBucketHistogramConfiguration();
|
||||
})
|
||||
.Build();
|
||||
|
||||
var attributes = ToAttributes(keysValues).ToArray();
|
||||
if (longValue.HasValue)
|
||||
{
|
||||
var histogram = meter.CreateHistogram<long>(name, unit, description);
|
||||
histogram.Record(longValue.Value, attributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
var histogram = meter.CreateHistogram<double>(name, unit, description);
|
||||
histogram.Record(doubleValue.Value, attributes);
|
||||
}
|
||||
|
||||
provider.ForceFlush();
|
||||
|
||||
var batch = new Batch<Metric>(metrics.ToArray(), metrics.Count);
|
||||
|
||||
var request = new OtlpCollector.ExportMetricsServiceRequest();
|
||||
request.AddMetrics(ResourceBuilder.CreateEmpty().Build().ToOtlpResource(), batch);
|
||||
|
||||
var resourceMetric = request.ResourceMetrics.Single();
|
||||
var scopeMetrics = resourceMetric.ScopeMetrics.Single();
|
||||
var actual = scopeMetrics.Metrics.Single();
|
||||
|
||||
Assert.Equal(name, actual.Name);
|
||||
Assert.Equal(description ?? string.Empty, actual.Description);
|
||||
Assert.Equal(unit ?? string.Empty, actual.Unit);
|
||||
|
||||
Assert.Equal(OtlpMetrics.Metric.DataOneofCase.ExponentialHistogram, actual.DataCase);
|
||||
|
||||
Assert.Null(actual.Gauge);
|
||||
Assert.Null(actual.Sum);
|
||||
Assert.Null(actual.Histogram);
|
||||
Assert.NotNull(actual.ExponentialHistogram);
|
||||
Assert.Null(actual.Summary);
|
||||
|
||||
var otlpAggregationTemporality = aggregationTemporality == MetricReaderTemporalityPreference.Cumulative
|
||||
? OtlpMetrics.AggregationTemporality.Cumulative
|
||||
: OtlpMetrics.AggregationTemporality.Delta;
|
||||
Assert.Equal(otlpAggregationTemporality, actual.ExponentialHistogram.AggregationTemporality);
|
||||
|
||||
Assert.Single(actual.ExponentialHistogram.DataPoints);
|
||||
var dataPoint = actual.ExponentialHistogram.DataPoints.First();
|
||||
Assert.True(dataPoint.StartTimeUnixNano > 0);
|
||||
Assert.True(dataPoint.TimeUnixNano > 0);
|
||||
|
||||
Assert.Equal(1UL, dataPoint.Count);
|
||||
|
||||
if (longValue.HasValue)
|
||||
{
|
||||
Assert.Equal((double)longValue, dataPoint.Sum);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(doubleValue, dataPoint.Sum);
|
||||
}
|
||||
|
||||
Assert.Equal(0UL, dataPoint.ZeroCount);
|
||||
Assert.Equal(20, dataPoint.Scale);
|
||||
Assert.True(dataPoint.Positive.Offset > 0);
|
||||
Assert.Equal(1UL, dataPoint.Positive.BucketCounts[0]);
|
||||
Assert.True(dataPoint.Negative.Offset <= 0);
|
||||
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
OtlpTestHelpers.AssertOtlpAttributes(attributes, dataPoint.Attributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Empty(dataPoint.Attributes);
|
||||
}
|
||||
|
||||
Assert.Empty(dataPoint.Exemplars);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)]
|
||||
[InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)]
|
||||
|
|
|
|||
|
|
@ -462,5 +462,30 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests
|
|||
+ "$").Replace('\'', '"'),
|
||||
Encoding.UTF8.GetString(buffer, 0, cursor));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExponentialHistogramIsIgnoredForNow()
|
||||
{
|
||||
var buffer = new byte[85000];
|
||||
var metrics = new List<Metric>();
|
||||
|
||||
using var meter = new Meter(Utils.GetCurrentMethodName());
|
||||
using var provider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter(meter.Name)
|
||||
.AddView(instrument => new Base2ExponentialBucketHistogramConfiguration())
|
||||
.AddInMemoryExporter(metrics)
|
||||
.Build();
|
||||
|
||||
var histogram = meter.CreateHistogram<double>("test_histogram");
|
||||
histogram.Record(18);
|
||||
histogram.Record(100);
|
||||
|
||||
provider.ForceFlush();
|
||||
|
||||
var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]);
|
||||
Assert.Matches(
|
||||
"^$",
|
||||
Encoding.UTF8.GetString(buffer, 0, cursor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue