Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Improve AzureMonitorScraper Error Handling for Time Series Missing Requested Dimension Value #2345

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/content/experimental/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version:

#### Scraper

- {{% tag fixed %}} Improve handling of time series with missing dimensions that are requested ([#2331](https://github.com/tomkerkhove/promitor/issues/2331))
- {{% tag added %}} Provide support for all label scenarios in StatsD & OpenTelemetry metric sink. This includes
dimensions, customer & default labels.
- {{% tag added %}} Provide support for scraping multiple metrics dimensions.
Expand Down
4 changes: 3 additions & 1 deletion src/Promitor.Core.Scraping/AzureMonitorScraper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ protected override async Task<ScrapeResult> ScrapeResourceAsync(string subscript
{
Logger.LogWarning("No metric information found for metric {MetricName} with dimensions {MetricDimensions}. Details: {Details}", metricsNotFoundException.Name, metricsNotFoundException.Dimensions, metricsNotFoundException.Details);

var measuredMetric = dimensionNames.Any() ? MeasuredMetric.CreateForDimensions(dimensionNames) : MeasuredMetric.CreateWithoutDimensions(null);
var measuredMetric = dimensionNames.Any()
? MeasuredMetric.CreateForDimensions(dimensionNames)
: MeasuredMetric.CreateWithoutDimensions(null);
measuredMetrics.Add(measuredMetric);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using GuardNet;
using Microsoft.Azure.Management.Monitor.Fluent.Models;

namespace Promitor.Core.Metrics.Exceptions
{
public class MissingDimensionException : Exception
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="dimensionName">Name of the dimension</param>
/// <param name="timeSeries">Time series element missing the dimension</param>
public MissingDimensionException(string dimensionName, TimeSeriesElement timeSeries) : base($"No value found for dimension '{dimensionName}'")
{
Guard.NotNullOrWhitespace(dimensionName, nameof(dimensionName));

DimensionName = dimensionName;
TimeSeries = timeSeries;
}

/// <summary>
/// Name of the dimension
/// </summary>
public string DimensionName { get; }

/// <summary>
/// Time series element producing the exception
/// </summary>
public TimeSeriesElement TimeSeries { get; }
}
}
11 changes: 8 additions & 3 deletions src/Promitor.Core/Metrics/MeasuredMetric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using GuardNet;
using Microsoft.Azure.Management.Monitor.Fluent.Models;
using Promitor.Core.Metrics.Exceptions;

namespace Promitor.Core.Metrics
{
Expand Down Expand Up @@ -57,12 +58,16 @@ public static MeasuredMetric CreateForDimensions(double? value, List<string> dim
{
Guard.NotAny(dimensionNames, nameof(dimensionNames));
Guard.NotNull(timeseries, nameof(timeseries));
Guard.For<ArgumentException>(() => timeseries.Metadatavalues.Any() == false);


var dimensions = new List<MeasuredMetricDimension>();
foreach (var dimensionName in dimensionNames)
{
var dimensionValue = timeseries.Metadatavalues.Single(metadataValue => metadataValue.Name?.Value.Equals(dimensionName, StringComparison.InvariantCultureIgnoreCase) == true).Value;
var dimensionMetadataValue = timeseries.Metadatavalues.Where(metadataValue => metadataValue.Name?.Value.Equals(dimensionName, StringComparison.InvariantCultureIgnoreCase) == true).ToList();
if(!dimensionMetadataValue.Any())
{
throw new MissingDimensionException(dimensionName, timeseries);
}
var dimensionValue = dimensionMetadataValue.Single().Value;
dimensions.Add(new MeasuredMetricDimension(dimensionName, dimensionValue));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Promitor.Integrations.AzureMonitor.Logging;
using Promitor.Integrations.AzureMonitor.RequestHandlers;
using Promitor.Integrations.Azure.Authentication;
using Promitor.Core.Metrics.Exceptions;

Check warning on line 25 in src/Promitor.Integrations.AzureMonitor/AzureMonitorClient.cs

View workflow job for this annotation

GitHub Actions / Code Quality (R#)

"[RedundantUsingDirective] Using directive is not required by the code and can be safely removed" on /home/runner/work/promitor/promitor/src/Promitor.Integrations.AzureMonitor/AzureMonitorClient.cs(25,979)
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved

namespace Promitor.Integrations.AzureMonitor
{
Expand Down Expand Up @@ -109,7 +110,9 @@
// Get the metric value according to the requested aggregation type
var requestedMetricAggregate = InterpretMetricValue(aggregationType, mostRecentMetricValue);

var measuredMetric = metricDimensions.Any() ? MeasuredMetric.CreateForDimensions(requestedMetricAggregate, metricDimensions, timeseries) : MeasuredMetric.CreateWithoutDimensions(requestedMetricAggregate);
var measuredMetric = metricDimensions.Any()
? MeasuredMetric.CreateForDimensions(requestedMetricAggregate, metricDimensions, timeseries)
: MeasuredMetric.CreateWithoutDimensions(requestedMetricAggregate);
measuredMetrics.Add(measuredMetric);
}

Expand Down
35 changes: 35 additions & 0 deletions src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.ComponentModel;
using Promitor.Core.Metrics;
using Microsoft.Azure.Management.Monitor.Fluent.Models;
using Xunit;
using Promitor.Core.Metrics.Exceptions;

namespace Promitor.Tests.Unit.Metrics
{
[Category("Unit")]
public class MeasuredMetricTest : UnitTest
{
[Fact]
public void Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds()
{
var dimensionNames = new List<string> { "dimTest"};
var dimensionValue = "dimTest1";
var timeSeries = new TimeSeriesElement(new List<MetadataValue> { new(name: new LocalizableString(dimensionNames[0]), value: dimensionValue)});
var measuredMetric = MeasuredMetric.CreateForDimensions(1, dimensionNames, timeSeries);
Assert.Equal(measuredMetric.Dimensions.Count, 1);

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values.

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Analyse

The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values.

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Verify Codebase

The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values.

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Verify Codebase

The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values.

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Verify Codebase

The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values.

Check warning on line 20 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Code Quality (R#)

"[xUnit2000] The literal or constant value 1 should be passed as the 'expected' argument in the call to 'Assert.Equal(expected, actual)' in method 'Create_MeasuredMetric_With_Single_Dimension_HappyPath_Succeeds' on type 'MeasuredMetricTest'. Swap the parameter values." on /home/runner/work/promitor/promitor/src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs(20,804)
Assert.Equal(measuredMetric.Dimensions[0].Name, dimensionNames[0]);
Assert.Equal(measuredMetric.Dimensions[0].Value, dimensionValue);
Assert.Equal(measuredMetric.Value, 1);
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void Create_MeasuredMetric_Missing_Dimension_Throws_Targeted_Exception()
{
var dimensionName = new List<string> { "dimTest"};
var timeSeries = new TimeSeriesElement(new List<MetadataValue> {});

Check warning on line 30 in src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs

View workflow job for this annotation

GitHub Actions / Code Quality (R#)

"[RedundantEmptyObjectOrCollectionInitializer] Empty object or collection initializer list is redundant" on /home/runner/work/promitor/promitor/src/Promitor.Tests.Unit/Metrics/MeasuredMetricTests.cs(30,1343)
tomkerkhove marked this conversation as resolved.
Show resolved Hide resolved
MissingDimensionException ex = Assert.Throws<MissingDimensionException>(() => MeasuredMetric.CreateForDimensions(1, dimensionName, timeSeries));
Assert.Equal(ex.DimensionName, dimensionName[0]);
}
}
}
Loading