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

feat: Provide support for batch scraping #2459

Merged
merged 136 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
163713d
define batch scrape definition model
hkfgo Mar 19, 2024
d0f4c73
implement skeleton batch scraping flow
hkfgo Mar 19, 2024
2552197
complete mapping logic between YAML and scraper domain
hkfgo Mar 20, 2024
975eef7
start grouping logic implementation via compound keys
hkfgo Mar 21, 2024
40289da
flesh out definiion batching logic
hkfgo Mar 21, 2024
e9e385a
implement scraper batch function + hashcode/equals for grouping
hkfgo Mar 23, 2024
d6fe4ff
implement scraper batch function + hashcode/equals for grouping
hkfgo Mar 23, 2024
55b88f9
Merge branch 'master' into feat/enable-batch-scraping
tomkerkhove May 30, 2024
6647ef1
github action image build
hkfgo May 2, 2024
77640a7
github action image build
hkfgo May 2, 2024
3b4ce25
Merge branch 'master' into feat/enable-batch-scraping
tomkerkhove Jul 15, 2024
9e7c0cd
code batch query client initialization
hkfgo Jul 17, 2024
c9bd29b
refactor resource query subroutines as extension methods
hkfgo Jul 18, 2024
43882d6
refactor single resource query subroutine as extension
hkfgo Jul 18, 2024
ff14367
code batch query execution and result processing
hkfgo Jul 19, 2024
3e3ef54
make some compile errors go away
hkfgo Jul 30, 2024
e290f90
make some compile errors go away
hkfgo Jul 30, 2024
c7a3fff
record batch size as histogram metric
hkfgo Jul 31, 2024
f35fce4
restrict batch scraping to only Azure Monitor scraper for now + defin…
hkfgo Aug 2, 2024
e8cf5e1
return resource ID tagged measure metrics in scrape path
hkfgo Aug 2, 2024
10f1599
cache resource info and use cache for hydration
hkfgo Aug 4, 2024
a5966fa
obtain testable build
hkfgo Aug 4, 2024
83761b2
unit test batch property hashcode and equality implementations
hkfgo Aug 8, 2024
94120b5
fix bug in MetricConfiguration to unique string implementation
hkfgo Aug 8, 2024
ca59552
move resource batching routines as separate static class
hkfgo Aug 8, 2024
106bfbd
unit test scrape definition batching logic
hkfgo Aug 9, 2024
cad0752
Move batching to runtime configuration
hkfgo Aug 14, 2024
30b535f
fix test
hkfgo Aug 14, 2024
963b71a
configure CI to do batch scraping
hkfgo Aug 14, 2024
e1a8365
add logging
hkfgo Aug 14, 2024
a93f065
add logging
hkfgo Aug 14, 2024
4342ae2
add logging on scraper
hkfgo Aug 14, 2024
6b0dcbb
add logging on scraper
hkfgo Aug 14, 2024
f773058
add logging on scraper
hkfgo Aug 14, 2024
c3aa159
log exception details
hkfgo Aug 15, 2024
3539362
add more logging on query tasks
hkfgo Aug 15, 2024
3cc8359
make region configurable
hkfgo Aug 16, 2024
7a8fc27
try fixing regional metrics URL
hkfgo Aug 16, 2024
bb71f0d
fix batch API URL formatting
hkfgo Aug 17, 2024
c79750d
fix batch API URL formatting
hkfgo Aug 17, 2024
5a1fc33
debug query range
hkfgo Aug 17, 2024
be7b18b
use different time range instantiation
hkfgo Aug 17, 2024
a58318f
use different time range instantiation
hkfgo Aug 17, 2024
187459c
lower case aggregations
hkfgo Aug 17, 2024
fdabad9
do not use size when filter not present
hkfgo Aug 17, 2024
b2cfd79
log filter
hkfgo Aug 17, 2024
dd9d248
log more query params
hkfgo Aug 17, 2024
a90bf15
log outgoing requests
hkfgo Aug 17, 2024
f2d51f2
try without time range first, because Azure SDK is buggy :(
hkfgo Aug 17, 2024
6406cbe
log ID resposne
hkfgo Aug 17, 2024
be8638c
try new regex for resource ID parsing
hkfgo Aug 19, 2024
3e8e095
process metrics results as IGroup
hkfgo Aug 19, 2024
694a856
log resource definition cache
hkfgo Aug 19, 2024
63d9a03
log resource definition cache
hkfgo Aug 19, 2024
71fb195
log individual resource definitions
hkfgo Aug 19, 2024
f02eaaa
fill out cached resource definitions
hkfgo Aug 19, 2024
6a0d2bf
fix fix
hkfgo Aug 19, 2024
78bf03c
correct aggregation interval processing
hkfgo Sep 9, 2024
7e583a9
try range query again
hkfgo Sep 9, 2024
802a9d2
try modify date range on outgoing requests
hkfgo Sep 10, 2024
b26c9aa
try modify date range on outgoing requests
hkfgo Sep 10, 2024
e85c255
try modify date range on outgoing requests
hkfgo Sep 10, 2024
0dba964
try modify date range on outgoing requests
hkfgo Sep 10, 2024
604ad76
run single resource scraping for comparison
hkfgo Sep 10, 2024
5b0b4cd
run single resource scraping for comparison
hkfgo Sep 10, 2024
cb1b696
go back to batch scraping CI
hkfgo Sep 10, 2024
834819d
create GitHub Action to build image under personal account(will revert)
hkfgo Sep 10, 2024
a3410b8
implement LogAnalytics batch scraping by composing single-resource sc…
hkfgo Sep 12, 2024
70e4a3f
fix bug writing histogram as gauge
hkfgo Sep 12, 2024
de785df
don't throw in OpenTelemetry sink
hkfgo Sep 12, 2024
0b463e7
don't throw in OpenTelemetry sink
hkfgo Sep 12, 2024
082a6e6
set better buckets for batch size
hkfgo Sep 12, 2024
beab4e6
correct logic to determine LogAnalytics aggregation interval
hkfgo Sep 12, 2024
6c721ab
add more debug logging
hkfgo Sep 12, 2024
5d8b5ef
handle single dimension in batching logic
hkfgo Sep 12, 2024
fc0362d
account for limit
hkfgo Sep 12, 2024
4dff71b
add debug logging for resource ID metrics
hkfgo Sep 12, 2024
cbc7e06
add forward slash in front of resource ID
hkfgo Sep 13, 2024
30aa153
improve regex matching
hkfgo Sep 13, 2024
2a42416
Merge branch 'master' into feat/enable-batch-scraping
hkfgo Sep 14, 2024
b77903d
add more debug logging
hkfgo Sep 17, 2024
a2da4a5
use configured max batch size
hkfgo Sep 17, 2024
124acbc
use cached resource definition
hkfgo Sep 18, 2024
c5fd2be
go back
hkfgo Sep 18, 2024
51e589f
go back
hkfgo Sep 18, 2024
3760629
use an older collector version
hkfgo Sep 19, 2024
4853b3e
use different string matching
hkfgo Sep 19, 2024
e2a013f
run prom exporter on localhost
hkfgo Sep 19, 2024
c29399a
try insecure flag
hkfgo Sep 19, 2024
027254b
fix style issues
hkfgo Sep 19, 2024
d0c7b41
use associated resource definition during processing
hkfgo Sep 19, 2024
46931e8
don't use resource-specific filters
hkfgo Sep 20, 2024
d4a0545
Merge branch 'master' into feat/enable-batch-scraping
tomkerkhove Sep 23, 2024
8a0e380
Merge branch 'master' into feat/enable-batch-scraping
tomkerkhove Sep 24, 2024
d88c53b
fix style issues
hkfgo Sep 25, 2024
a853c26
fix style
hkfgo Sep 25, 2024
430c725
Address comments
hkfgo Sep 26, 2024
11d765c
more style fixes
hkfgo Sep 26, 2024
5d38684
consolidate some logging
hkfgo Sep 26, 2024
b3eb049
more style fixes :(
hkfgo Sep 26, 2024
9073887
add null check
hkfgo Sep 26, 2024
e1c96b1
avoid naming collision
hkfgo Sep 26, 2024
172cd33
fix fix style
hkfgo Sep 26, 2024
299ec6e
make config settable
hkfgo Sep 26, 2024
b2d3458
get rid of some redundant code
hkfgo Sep 26, 2024
47672d6
Fighting resharper
hkfgo Sep 26, 2024
06e404d
Fighting resharper
hkfgo Sep 26, 2024
af638b0
add null check
hkfgo Sep 26, 2024
4573300
add null check
hkfgo Sep 26, 2024
9f25804
add simple unit test
hkfgo Sep 26, 2024
943e437
Revert CI workflow changes
hkfgo Sep 26, 2024
88f43cc
get rid of excessive logging
hkfgo Sep 26, 2024
c790b54
add a link to docs
hkfgo Sep 26, 2024
ce56dbb
close ref
hkfgo Sep 26, 2024
04ff115
do not initialize batch client under single-resource scraping
hkfgo Sep 26, 2024
31669c5
handle windows time
hkfgo Sep 26, 2024
b82b73d
(temporary) change GitHub action to build another Linux image
hkfgo Sep 30, 2024
c4b1041
revert back GitHub Action chagnes
hkfgo Oct 1, 2024
fa2fc3f
ci changes
hkfgo Oct 1, 2024
216812b
add logging
hkfgo Oct 1, 2024
c7a4a6a
add logging
hkfgo Oct 1, 2024
e27d581
add logging
hkfgo Oct 1, 2024
f1a2c48
add logging
hkfgo Oct 1, 2024
c72bda1
disable ReSharper when modifying outgoing URL
hkfgo Oct 1, 2024
206e155
remove some more logging + add null checks
hkfgo Oct 1, 2024
67bf0a5
remove some more logging + add null checks
hkfgo Oct 1, 2024
f04cb87
remove some more logging + add null checks
hkfgo Oct 1, 2024
377bdfd
remove some more logging + add null checks
hkfgo Oct 1, 2024
431f37d
remove some more logging + add null checks
hkfgo Oct 1, 2024
86b4ddd
do not run batch mode in CI
hkfgo Oct 1, 2024
eb6d676
trigger CI again
hkfgo Oct 1, 2024
f2e0087
Modify workflow file again
hkfgo Oct 1, 2024
fedf814
revert GitHubaction
hkfgo Oct 1, 2024
988da9b
remove more debug logging
hkfgo Oct 1, 2024
fd14f63
retry-ci
hkfgo Oct 1, 2024
1face30
retry-ci
hkfgo Oct 2, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/templates-build-push-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ jobs:
context: ./src/
file: ./src/${{ inputs.project_name }}/Dockerfile.linux
tags: ${{ env.image_commit_uri }},${{ env.image_latest_uri }}
push: true
push: true
58 changes: 48 additions & 10 deletions src/Promitor.Agents.Scraper/Scheduling/ResourcesScrapingJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Promitor.Core.Extensions;
using Promitor.Core.Metrics.Interfaces;
using Promitor.Core.Metrics.Sinks;
using Promitor.Core.Scraping.Batching;
using Promitor.Core.Scraping.Configuration.Model;
using Promitor.Core.Scraping.Configuration.Model.Metrics;
using Promitor.Core.Scraping.Factories;
Expand Down Expand Up @@ -134,7 +135,6 @@ public async Task ExecuteAsync(CancellationToken cancellationToken)
try
{
var scrapeDefinitions = await GetAllScrapeDefinitions(cancellationToken);

await ScrapeMetrics(scrapeDefinitions, cancellationToken);
}
catch (OperationCanceledException)
Expand Down Expand Up @@ -251,22 +251,59 @@ private void GetResourceScrapeDefinition(IAzureResourceDefinition resourceDefini
}

private async Task ScrapeMetrics(IEnumerable<ScrapeDefinition<IAzureResourceDefinition>> scrapeDefinitions, CancellationToken cancellationToken)
{
{
var tasks = new List<Task>();
var batchScrapingEnabled = this._azureMonitorIntegrationConfiguration.Value.MetricsBatching?.Enabled ?? false;
if (batchScrapingEnabled) {
Logger.LogInformation("Promitor Scraper with operate in batch scraping mode, with max batch size {BatchSize}", this._azureMonitorIntegrationConfiguration.Value.MetricsBatching.MaxBatchSize);
Logger.LogWarning("Batch scraping is an experimental feature. See Promitor.io for its limitations and cost considerations");

var batchScrapeDefinitions = AzureResourceDefinitionBatching.GroupScrapeDefinitions(scrapeDefinitions, this._azureMonitorIntegrationConfiguration.Value.MetricsBatching.MaxBatchSize);

foreach(var batchScrapeDefinition in batchScrapeDefinitions) {
var azureMetricName = batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration.MetricName;
var resourceType = batchScrapeDefinition.ScrapeDefinitionBatchProperties.ResourceType;
Logger.LogInformation("Executing batch scrape job of size {BatchSize} for Azure Metric {AzureMetricName} for resource type {ResourceType}.", batchScrapeDefinition.ScrapeDefinitions.Count, azureMetricName, resourceType);
await ScheduleLimitedConcurrencyAsyncTask(tasks, () => ScrapeMetricBatched(batchScrapeDefinition), cancellationToken);
}
} else {
foreach (var scrapeDefinition in scrapeDefinitions)
{
cancellationToken.ThrowIfCancellationRequested();

foreach (var scrapeDefinition in scrapeDefinitions)
{
cancellationToken.ThrowIfCancellationRequested();

var metricName = scrapeDefinition.PrometheusMetricDefinition.Name;
var resourceType = scrapeDefinition.Resource.ResourceType;
Logger.LogInformation("Scraping {MetricName} for resource type {ResourceType}.", metricName, resourceType);
var metricName = scrapeDefinition.PrometheusMetricDefinition.Name;
var resourceType = scrapeDefinition.Resource.ResourceType;
Logger.LogInformation("Scraping {MetricName} for resource type {ResourceType}.", metricName, resourceType);

await ScheduleLimitedConcurrencyAsyncTask(tasks, () => ScrapeMetric(scrapeDefinition), cancellationToken);
await ScheduleLimitedConcurrencyAsyncTask(tasks, () => ScrapeMetric(scrapeDefinition), cancellationToken);
}
}

await Task.WhenAll(tasks);
}
private async Task ScrapeMetricBatched(BatchScrapeDefinition<IAzureResourceDefinition> batchScrapeDefinition) {
try
{
var resourceSubscriptionId = batchScrapeDefinition.ScrapeDefinitionBatchProperties.SubscriptionId;
var azureMonitorClient = _azureMonitorClientFactory.CreateIfNotExists(_metricsDeclaration.AzureMetadata.Cloud, _metricsDeclaration.AzureMetadata.TenantId,
resourceSubscriptionId, _metricSinkWriter, _azureScrapingSystemMetricsPublisher, _resourceMetricDefinitionMemoryCache, _configuration,
_azureMonitorIntegrationConfiguration, _azureMonitorLoggingConfiguration, _loggerFactory);
var azureEnvironent = _metricsDeclaration.AzureMetadata.Cloud.GetAzureEnvironment();

var tokenCredential = AzureAuthenticationFactory.GetTokenCredential(azureEnvironent.ManagementEndpoint, _metricsDeclaration.AzureMetadata.TenantId,
AzureAuthenticationFactory.GetConfiguredAzureAuthentication(_configuration), new Uri(_metricsDeclaration.AzureMetadata.Cloud.GetAzureEnvironment().AuthenticationEndpoint));
var logAnalyticsClient = new LogAnalyticsClient(_loggerFactory, azureEnvironent, tokenCredential);

var scraper = _metricScraperFactory.CreateScraper(batchScrapeDefinition.ScrapeDefinitionBatchProperties.ResourceType, _metricSinkWriter, _azureScrapingSystemMetricsPublisher, azureMonitorClient, logAnalyticsClient);

await scraper.BatchScrapeAsync(batchScrapeDefinition);
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to scrape metric {MetricName} for resource batch {ResourceName}. Details: {Details}",
batchScrapeDefinition.ScrapeDefinitionBatchProperties.PrometheusMetricDefinition.Name, batchScrapeDefinition.ScrapeDefinitionBatchProperties.ResourceType, ex.ToString());
}
}

private async Task ScrapeMetric(ScrapeDefinition<IAzureResourceDefinition> scrapeDefinition)
{
Expand All @@ -287,6 +324,7 @@ private async Task ScrapeMetric(ScrapeDefinition<IAzureResourceDefinition> scrap
var logAnalyticsClient = new LogAnalyticsClient(_loggerFactory, azureEnvironent, tokenCredential);

var scraper = _metricScraperFactory.CreateScraper(scrapeDefinition.Resource.ResourceType, _metricSinkWriter, _azureScrapingSystemMetricsPublisher, azureMonitorClient, logAnalyticsClient);

await scraper.ScrapeAsync(scrapeDefinition);
}
catch (Exception ex)
Expand Down
78 changes: 75 additions & 3 deletions src/Promitor.Core.Scraping/AzureMonitorScraper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using GuardNet;
using Microsoft.Extensions.Logging;
using Promitor.Core.Contracts;
using Promitor.Core.Extensions;
using Promitor.Core.Metrics;
using Promitor.Core.Scraping.Configuration.Model;
using Promitor.Core.Scraping.Configuration.Model.Metrics;
Expand All @@ -18,13 +21,19 @@ namespace Promitor.Core.Scraping
/// <typeparam name="TResourceDefinition">Type of metric definition that is being used</typeparam>
public abstract class AzureMonitorScraper<TResourceDefinition> : Scraper<TResourceDefinition>
where TResourceDefinition : class, IAzureResourceDefinition
{
{
/// <summary>
/// A cache to store resource definitions. Used to hydrate resource info from resource ID, when processing batch query results
/// </summary>
private readonly ConcurrentDictionary<string, Tuple<IAzureResourceDefinition, TResourceDefinition>> _resourceDefinitions; // using a dictionary for now since IMemoryCache involves layers of injection

/// <summary>
/// Constructor
/// </summary>
protected AzureMonitorScraper(ScraperConfiguration scraperConfiguration) :
base(scraperConfiguration)
{
_resourceDefinitions = new ConcurrentDictionary<string, Tuple<IAzureResourceDefinition, TResourceDefinition>>();
}

/// <inheritdoc />
Expand Down Expand Up @@ -73,6 +82,69 @@ protected override async Task<ScrapeResult> ScrapeResourceAsync(string subscript
return new ScrapeResult(subscriptionId, scrapeDefinition.ResourceGroupName, resourceDefinition.ResourceName, resourceUri, finalMetricValues, metricLabels);
}

protected override async Task<List<ScrapeResult>> BatchScrapeResourceAsync(string subscriptionId, BatchScrapeDefinition<IAzureResourceDefinition> batchScrapeDefinition, PromitorMetricAggregationType aggregationType, TimeSpan aggregationInterval)
{
Guard.NotNull(batchScrapeDefinition, nameof(batchScrapeDefinition));
Guard.NotLessThan(batchScrapeDefinition.ScrapeDefinitions.Count(), 1, nameof(batchScrapeDefinition));
Guard.NotNull(batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration, nameof(batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration));

var metricName = batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration.MetricName;

// Build list of resource URIs based on definitions in the batch
var resourceUriList = new List<string>();
foreach (ScrapeDefinition<IAzureResourceDefinition> scrapeDefinition in batchScrapeDefinition.ScrapeDefinitions)
{
var resourceUri = $"/{BuildResourceUri(subscriptionId, scrapeDefinition, (TResourceDefinition) scrapeDefinition.Resource)}";
resourceUriList.Add(resourceUri);
// cache resource info
// the TResourceDefinition resource definition attached to scrape definition can sometimes missing some attributes, need to them in here
var resourceDefinitionToCache = new AzureResourceDefinition
(
resourceType: scrapeDefinition.Resource.ResourceType,
resourceGroupName: scrapeDefinition.ResourceGroupName,
subscriptionId: scrapeDefinition.SubscriptionId,
resourceName: scrapeDefinition.Resource.ResourceName
);
_resourceDefinitions.AddOrUpdate(resourceUri, new Tuple<IAzureResourceDefinition, TResourceDefinition>(resourceDefinitionToCache, (TResourceDefinition)scrapeDefinition.Resource), (newTuple, oldTuple) => oldTuple);
}

var metricLimit = batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration.Limit;
var dimensionNames = DetermineMetricDimensions(metricName, (TResourceDefinition) batchScrapeDefinition.ScrapeDefinitions[0].Resource, batchScrapeDefinition.ScrapeDefinitionBatchProperties.AzureMetricConfiguration); // TODO: resource definition doesn't seem to be used, can we remove it from function signature?
hkfgo marked this conversation as resolved.
Show resolved Hide resolved

var resourceIdTaggedMeasuredMetrics = new List<ResourceAssociatedMeasuredMetric>();
try
{
// Query Azure Monitor for metrics
resourceIdTaggedMeasuredMetrics = await AzureMonitorClient.BatchQueryMetricAsync(metricName, dimensionNames, aggregationType, aggregationInterval, resourceUriList, null, metricLimit);
}
catch (MetricInformationNotFoundException metricsNotFoundException)
{
Logger.LogWarning("No metric information found for metric {MetricName} with dimensions {MetricDimensions}. Details: {Details}", metricsNotFoundException.Name, metricsNotFoundException.Dimensions, metricsNotFoundException.Details);

var measuredMetric = dimensionNames.Count > 0
? MeasuredMetric.CreateForDimensions(dimensionNames)
: MeasuredMetric.CreateWithoutDimensions(null);
resourceIdTaggedMeasuredMetrics.Add(measuredMetric.WithResourceIdAssociation(null));
}

var scrapeResults = new List<ScrapeResult>();
// group based on resource, then do enrichment per group
var groupedMeasuredMetrics = resourceIdTaggedMeasuredMetrics.GroupBy(measuredMetric => measuredMetric.ResourceId);
foreach (IGrouping<string, ResourceAssociatedMeasuredMetric> resourceMetricsGroup in groupedMeasuredMetrics)
{
var resourceId = resourceMetricsGroup.Key;
if (_resourceDefinitions.TryGetValue(resourceId, out Tuple<IAzureResourceDefinition, TResourceDefinition> resourceDefinitionTuple))
{
var resourceDefinition = resourceDefinitionTuple.Item1;
var metricLabels = DetermineMetricLabels(resourceDefinitionTuple.Item2);
var finalMetricValues = EnrichMeasuredMetrics(resourceDefinitionTuple.Item2, dimensionNames, resourceMetricsGroup.ToImmutableList());
scrapeResults.Add(new ScrapeResult(subscriptionId, resourceDefinition.ResourceGroupName, resourceDefinition.ResourceName, resourceId, finalMetricValues, metricLabels));
}
}

return scrapeResults;
}

private int? DetermineMetricLimit(ScrapeDefinition<IAzureResourceDefinition> scrapeDefinition)
{
return scrapeDefinition.AzureMetricConfiguration.Limit;
Expand All @@ -89,9 +161,9 @@ protected override async Task<ScrapeResult> ScrapeResourceAsync(string subscript
/// <param name="dimensionNames">List of names of the specified dimensions provided by the scraper.</param>
/// <param name="metricValues">Measured metric values that were found</param>
/// <returns></returns>
protected virtual List<MeasuredMetric> EnrichMeasuredMetrics(TResourceDefinition resourceDefinition, List<string> dimensionNames, List<MeasuredMetric> metricValues)
protected virtual List<MeasuredMetric> EnrichMeasuredMetrics(TResourceDefinition resourceDefinition, List<string> dimensionNames, IReadOnlyList<MeasuredMetric> metricValues)
{
return metricValues;
return metricValues.ToList();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Linq;
using GuardNet;
using Promitor.Core.Contracts;
using Promitor.Core.Scraping.Configuration.Model.Metrics;

namespace Promitor.Core.Scraping.Batching
{
public static class AzureResourceDefinitionBatching
{
/// <summary>
/// groups scrape definitions based on following conditions:
/// 1. Definitions in a batch must target the same resource type
/// 2. Definitions in a batch must target the same Azure metric with identical dimensions
/// 3. Definitions in a batch must have the same time granularity
/// 4. Batch size cannot exceed configured maximum
/// <see href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/migrate-to-batch-api?tabs=individual-response#batching-restrictions"/>
/// </summary>
public static List<BatchScrapeDefinition<IAzureResourceDefinition>> GroupScrapeDefinitions(IEnumerable<ScrapeDefinition<IAzureResourceDefinition>> allScrapeDefinitions, int maxBatchSize)
{
hkfgo marked this conversation as resolved.
Show resolved Hide resolved
// ReSharper disable PossibleMultipleEnumeration
Guard.NotNull(allScrapeDefinitions, nameof(allScrapeDefinitions));

return allScrapeDefinitions.GroupBy(def => def.BuildScrapingBatchInfo())
.ToDictionary(group => group.Key, group => group.ToList()) // first pass to build batches that could exceed max
.ToDictionary(group => group.Key, group => SplitScrapeDefinitionBatch(group.Value, maxBatchSize)) // split to right-sized batches
.SelectMany(group => group.Value.Select(batch => new BatchScrapeDefinition<IAzureResourceDefinition>(group.Key, batch)))
.ToList(); // flatten
}

/// <summary>
/// splits the "raw" batch according to max batch size configured
/// </summary>
private static List<List<ScrapeDefinition<IAzureResourceDefinition>>> SplitScrapeDefinitionBatch(List<ScrapeDefinition<IAzureResourceDefinition>> batchToSplit, int maxBatchSize)
{
hkfgo marked this conversation as resolved.
Show resolved Hide resolved
// ReSharper disable PossibleMultipleEnumeration
Guard.NotNull(batchToSplit, nameof(batchToSplit));

int numNewGroups = ((batchToSplit.Count - 1) / maxBatchSize) + 1;

return Enumerable.Range(0, numNewGroups)
.Select(i => batchToSplit.Skip(i * maxBatchSize).Take(maxBatchSize).ToList())
.ToList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Promitor.Core.Scraping.Configuration.Model
{
Expand Down Expand Up @@ -45,5 +46,29 @@ public class AzureMetricConfiguration
}
return Dimensions?.Any(dimension => dimension.Name.Equals(dimensionName, StringComparison.InvariantCultureIgnoreCase));
}

// A unique string to represent this Azure metric and its configured dimensions
public string ToUniqueStringRepresentation()
hkfgo marked this conversation as resolved.
Show resolved Hide resolved
{
StringBuilder sb = new StringBuilder();
sb.Append(MetricName);
if (Dimension != null)
{
sb.Append('_');
sb.Append(Dimension.Name);
}
else if (Dimensions != null)
{
foreach (var dimension in Dimensions)
{
sb.Append('_');
sb.Append(dimension.Name);
}
}
sb.Append($"_limit{Limit}");


return sb.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using GuardNet;
using Promitor.Core.Contracts;

namespace Promitor.Core.Scraping.Configuration.Model.Metrics
{
/// <summary>
/// Defines a batch of ScrapeDefinitions to be executed in a single request
/// Scrape definitions within a batch should share
/// 1. The same resource type
/// 2. The same Azure metric scrape target with identical dimensions
/// 3. The same time granularity
/// 4. The same filters
/// </summary>
public class BatchScrapeDefinition<TResourceDefinition> where TResourceDefinition : class, IAzureResourceDefinition
{
/// <summary>
/// Creates a new instance of the <see cref="BatchScrapeDefinition{TResourceDefinition}"/> class.
/// </summary>
/// <param name="scrapeDefinitionBatchProperties">Shared Properties Among ScrapeDefinition's in the batch</param>
/// <param name="groupedScrapeDefinitions">Scape definitions in the batch</param>
public BatchScrapeDefinition(ScrapeDefinitionBatchProperties scrapeDefinitionBatchProperties, List<ScrapeDefinition<TResourceDefinition>> groupedScrapeDefinitions)
{
Guard.NotNull(groupedScrapeDefinitions, nameof(groupedScrapeDefinitions));
Guard.NotLessThan(groupedScrapeDefinitions.Count, 1, nameof(groupedScrapeDefinitions));
Guard.NotNull(scrapeDefinitionBatchProperties, nameof(scrapeDefinitionBatchProperties));

ScrapeDefinitionBatchProperties = scrapeDefinitionBatchProperties;
ScrapeDefinitions = groupedScrapeDefinitions;
}

/// <summary>
/// A batch of scrape job definitions to be executed as a single request
/// </summary>
public List<ScrapeDefinition<TResourceDefinition>> ScrapeDefinitions { get; set; }

public ScrapeDefinitionBatchProperties ScrapeDefinitionBatchProperties { get; set; }
}
}
Loading
Loading