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

Support for multiple resource groups #442

Merged
merged 12 commits into from
Mar 26, 2019
19 changes: 18 additions & 1 deletion docs/configuration/metrics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Metrics Declaration
All the Azure Monitor metrics that needs to be scraped are consolidated in one YAML file.
This configuration defines the Azure metadata and all the metrics.

Here is an example of how you can scrape an Azure Service Bus queue:
Here is an example of how you can scrape two Azure Service Bus queues in different resource groups, one in the `promitor` resource group and one on the `promitor-dev` resource group:

```yaml
azureMetadata:
Expand All @@ -27,9 +27,21 @@ metrics:
aggregation:
type: Total
interval: 00:15:00
- name: demo_queue_dev_size
description: "Amount of active messages of the 'myqueue-dev' queue"
resourceType: ServiceBusQueue
namespace: promitor-messaging-dev
queueName: orders
resourceGroupName: promitor-dev
azureMetricConfiguration:
metricName: ActiveMessages
aggregation:
type: Total
interval: 00:15:00
```

# General Declaration

## Azure

- `azureMetadata.tenantId` - The id of the Azure tenant that will be queried.
Expand All @@ -41,18 +53,23 @@ metrics:
- `metricDefaults.aggregation.interval` - The default interval which defines over what period measurements of a metric should be aggregated.

## Metrics

Every metric that is being declared needs to define the following fields:

- `name` - Name of the metric that will be exposed in the scrape endpoint for Prometheus
- `description` - Description for the metric that will be exposed in the scrape endpoint for Prometheus
- `resourceType` - Defines what type of resource needs to be queried.
- `resourceGroupName` - _(Optional)_ Capability to specify a resource group to scrape for this metric. This allows you to specify a different resource group from the one configured in `azureMetadata`. This allows you to scrape multiple resource groups with one single configuration.
- `azureMetricConfiguration.metricName` - The name of the metric in Azure Monitor to query
- `azureMetricConfiguration.aggregation.type` - The aggregation that needs to be used when querying Azure Monitor
- `azureMetricConfiguration.aggregation.interval` - Overrides the default aggregation interval defined in `metricDefaults.aggregation.interval` with a new interval

# Supported Azure Services

Every Azure service is supported and can be scraped by using the [Generic Azure Resource](generic-azure-resource).

We also provide a simplified way to configure the following Azure resources:

- [Azure Container Instances](container-instances)
- [Azure Service Bus Queue](service-bus-queue)
- [Azure Storage Queue](storage-queue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public abstract class MetricDefinition
/// </summary>
public string Name { get; set; }

/// <summary>
/// Specify a resource group to scrape that defers from the default resource group.
/// This enables you to do multi-resource group scraping with one configuration file.
/// </summary>
public string ResourceGroupName { get; set; }

/// <summary>
/// Type of resource that is configured
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ internal override MetricDefinition Deserialize(YamlMappingNode metricNode)
if (metricNode.Children.TryGetValue(new YamlScalarNode(value: "filter"), out var filterNode))
{
metricDefinition.Filter = filterNode?.ToString();
}
}
if (metricNode.Children.TryGetValue(new YamlScalarNode("resourceGroupName"), out var resourceGroupName))
{
metricDefinition.ResourceGroupName = resourceGroupName?.ToString();
}

var resourceUri = metricNode.Children[new YamlScalarNode(value: "resourceUri")];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public ContainerInstanceScraper(AzureMetadata azureMetadata, MetricDefaults metr
{
}

protected override async Task<double> ScrapeResourceAsync(ContainerInstanceMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
protected override async Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, ContainerInstanceMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, AzureMetadata.ResourceGroupName, metricDefinition.ContainerGroup);

Expand Down
4 changes: 2 additions & 2 deletions src/Promitor.Core.Scraping/ResourceTypes/GenericScraper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public GenericScraper(AzureMetadata azureMetadata, MetricDefaults metricDefaults
{
}

protected override async Task<double> ScrapeResourceAsync(GenericAzureMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
protected override async Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, GenericAzureMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, AzureMetadata.ResourceGroupName, metricDefinition.ResourceUri);
var resourceUri = string.Format(ResourceUriTemplate, subscriptionId, resourceGroupName, metricDefinition.ResourceUri);
var metricName = metricDefinition.AzureMetricConfiguration.MetricName;
var foundMetricValue = await AzureMonitorClient.QueryMetricAsync(metricName, aggregationType, aggregationInterval, resourceUri, metricDefinition.Filter);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public ServiceBusQueueScraper(AzureMetadata azureMetadata, MetricDefaults metric
{
}

protected override async Task<double> ScrapeResourceAsync(ServiceBusQueueMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
protected override async Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, ServiceBusQueueMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, AzureMetadata.ResourceGroupName, metricDefinition.Namespace);
var resourceUri = string.Format(ResourceUriTemplate, subscriptionId, resourceGroupName, metricDefinition.Namespace);

var filter = $"EntityName eq '{metricDefinition.QueueName}'";
var metricName = metricDefinition.AzureMetricConfiguration.MetricName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public StorageQueueScraper(AzureMetadata azureMetadata, MetricDefaults metricDef
_azureStorageQueueClient = new AzureStorageQueueClient(logger);
}

protected override async Task<double> ScrapeResourceAsync(StorageQueueMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
protected override async Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, StorageQueueMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
Guard.NotNull(metricDefinition, nameof(metricDefinition));
Guard.NotNull(metricDefinition.AzureMetricConfiguration, nameof(metricDefinition.AzureMetricConfiguration));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public VirtualMachineScraper(AzureMetadata azureMetadata, MetricDefaults metricD
{
}

protected override async Task<double> ScrapeResourceAsync(VirtualMachineMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
protected override async Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, VirtualMachineMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
var resourceUri = string.Format(ResourceUriTemplate, AzureMetadata.SubscriptionId, AzureMetadata.ResourceGroupName, metricDefinition.VirtualMachineName);

Expand Down
250 changes: 127 additions & 123 deletions src/Promitor.Core.Scraping/Scraper.cs
Original file line number Diff line number Diff line change
@@ -1,123 +1,127 @@
using System;
using System.Threading.Tasks;
using GuardNet;
using Microsoft.Azure.Management.Monitor.Fluent.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Prometheus.Client;
using Promitor.Core.Infrastructure;
using Promitor.Core.Scraping.Configuration.Model;
using Promitor.Core.Scraping.Interfaces;
using Promitor.Core.Telemetry.Interfaces;
using Promitor.Integrations.AzureMonitor;
using MetricDefinition = Promitor.Core.Scraping.Configuration.Model.Metrics.MetricDefinition;
// ReSharper disable All

namespace Promitor.Core.Scraping
{
/// <summary>
/// Azure Monitor Scrape
/// </summary>
/// <typeparam name="TMetricDefinition">Type of metric definition that is being used</typeparam>
public abstract class Scraper<TMetricDefinition> : IScraper<MetricDefinition>
where TMetricDefinition : MetricDefinition, new()
{
private readonly IExceptionTracker _exceptionTracker;
private readonly ILogger _logger;

/// <summary>
/// Constructor
/// </summary>
/// <param name="azureMetadata">Metadata concerning the Azure resources</param>
/// <param name="azureMonitorClient">Client to communicate with Azure Monitor</param>
/// <param name="metricDefaults">Default configuration for metrics</param>
/// <param name="logger">General logger</param>
/// <param name="exceptionTracker">Exception tracker</param>
protected Scraper(AzureMetadata azureMetadata, MetricDefaults metricDefaults, AzureMonitorClient azureMonitorClient,
ILogger logger, IExceptionTracker exceptionTracker)
{
Guard.NotNull(exceptionTracker, nameof(exceptionTracker));
Guard.NotNull(azureMetadata, nameof(azureMetadata));
Guard.NotNull(metricDefaults, nameof(metricDefaults));
Guard.NotNull(azureMonitorClient, nameof(azureMonitorClient));

_logger = logger;
_exceptionTracker = exceptionTracker;

AzureMetadata = azureMetadata;
MetricDefaults = metricDefaults;
AzureMonitorClient = azureMonitorClient;
}

/// <summary>
/// Metadata concerning the Azure resources
/// </summary>
protected AzureMetadata AzureMetadata { get; }

/// <summary>
/// Default configuration for metrics
/// </summary>
protected MetricDefaults MetricDefaults { get; }

/// <summary>
/// Client to interact with Azure Monitor
/// </summary>
protected AzureMonitorClient AzureMonitorClient { get; }

public async Task ScrapeAsync(MetricDefinition metricDefinition)
{
try
{
if (metricDefinition == null)
{
throw new ArgumentNullException(nameof(metricDefinition));
}

if (!(metricDefinition is TMetricDefinition castedMetricDefinition))
{
throw new ArgumentException($"Could not cast metric definition of type '{metricDefinition.ResourceType}' to {typeof(TMetricDefinition)}. Payload: {JsonConvert.SerializeObject(metricDefinition)}");
}

var aggregationInterval = DetermineMetricAggregationInterval(metricDefinition);
var aggregationType = metricDefinition.AzureMetricConfiguration.Aggregation.Type;
var foundMetricValue = await ScrapeResourceAsync(castedMetricDefinition, aggregationType, aggregationInterval);

_logger.LogInformation("Found value '{MetricValue}' for metric '{MetricName}' with aggregation interval '{AggregationInterval}'", foundMetricValue, metricDefinition.Name, aggregationInterval);

var metricsTimestampFeatureFlag = FeatureFlag.IsActive("METRICSTIMESTAMP", defaultFlagState: true);

var gauge = Metrics.CreateGauge(metricDefinition.Name, metricDefinition.Description, includeTimestamp: metricsTimestampFeatureFlag);
gauge.Set(foundMetricValue);
}
catch (Exception exception)
{
_exceptionTracker.Track(exception);
}
}

private TimeSpan DetermineMetricAggregationInterval(MetricDefinition metricDefinition)
{
Guard.NotNull(metricDefinition, nameof(metricDefinition));

if (metricDefinition?.AzureMetricConfiguration?.Aggregation?.Interval != null)
{
return metricDefinition.AzureMetricConfiguration.Aggregation.Interval.Value;
}

if (MetricDefaults.Aggregation.Interval == null)
{
throw new Exception($"No default aggregation interval is configured nor on the metric configuration for '{metricDefinition?.Name}'");
}

return MetricDefaults.Aggregation.Interval.Value;
}

/// <summary>
/// Scrapes the configured resource
/// </summary>
/// <param name="metricDefinition">Definition of the metric to scrape</param>
/// <param name="aggregationType">Aggregation for the metric to use</param>
/// <param name="aggregationInterval">Interval that is used to aggregate metrics</param>
protected abstract Task<double> ScrapeResourceAsync(TMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval);
}
}
using System;
using System.Threading.Tasks;
using GuardNet;
using Microsoft.Azure.Management.Monitor.Fluent.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Prometheus.Client;
using Promitor.Core.Infrastructure;
using Promitor.Core.Scraping.Configuration.Model;
using Promitor.Core.Scraping.Interfaces;
using Promitor.Core.Telemetry.Interfaces;
using Promitor.Integrations.AzureMonitor;
using MetricDefinition = Promitor.Core.Scraping.Configuration.Model.Metrics.MetricDefinition;
// ReSharper disable All

namespace Promitor.Core.Scraping
{
/// <summary>
/// Azure Monitor Scraper
/// </summary>
/// <typeparam name="TMetricDefinition">Type of metric definition that is being used</typeparam>
public abstract class Scraper<TMetricDefinition> : IScraper<MetricDefinition>
where TMetricDefinition : MetricDefinition, new()
{
private readonly IExceptionTracker _exceptionTracker;
private readonly ILogger _logger;

/// <summary>
/// Constructor
/// </summary>
/// <param name="azureMetadata">Metadata concerning the Azure resources</param>
/// <param name="azureMonitorClient">Client to communicate with Azure Monitor</param>
/// <param name="metricDefaults">Default configuration for metrics</param>
/// <param name="logger">General logger</param>
/// <param name="exceptionTracker">Exception tracker</param>
protected Scraper(AzureMetadata azureMetadata, MetricDefaults metricDefaults, AzureMonitorClient azureMonitorClient,
ILogger logger, IExceptionTracker exceptionTracker)
{
Guard.NotNull(exceptionTracker, nameof(exceptionTracker));
Guard.NotNull(azureMetadata, nameof(azureMetadata));
Guard.NotNull(metricDefaults, nameof(metricDefaults));
Guard.NotNull(azureMonitorClient, nameof(azureMonitorClient));

_logger = logger;
_exceptionTracker = exceptionTracker;

AzureMetadata = azureMetadata;
MetricDefaults = metricDefaults;
AzureMonitorClient = azureMonitorClient;
}

/// <summary>
/// Metadata concerning the Azure resources
/// </summary>
protected AzureMetadata AzureMetadata { get; }

/// <summary>
/// Default configuration for metrics
/// </summary>
protected MetricDefaults MetricDefaults { get; }

/// <summary>
/// Client to interact with Azure Monitor
/// </summary>
protected AzureMonitorClient AzureMonitorClient { get; }

public async Task ScrapeAsync(MetricDefinition metricDefinition)
{
try
{
if (metricDefinition == null)
{
throw new ArgumentNullException(nameof(metricDefinition));
}

if (!(metricDefinition is TMetricDefinition castedMetricDefinition))
{
throw new ArgumentException($"Could not cast metric definition of type '{metricDefinition.ResourceType}' to {typeof(TMetricDefinition)}. Payload: {JsonConvert.SerializeObject(metricDefinition)}");
}

var aggregationInterval = DetermineMetricAggregationInterval(metricDefinition);
var aggregationType = metricDefinition.AzureMetricConfiguration.Aggregation.Type;
var resourceGroupName = string.IsNullOrEmpty(metricDefinition.ResourceGroupName) ? AzureMetadata.ResourceGroupName : metricDefinition.ResourceGroupName;
var foundMetricValue = await ScrapeResourceAsync(AzureMetadata.SubscriptionId, resourceGroupName, castedMetricDefinition, aggregationType, aggregationInterval);

_logger.LogInformation("Found value '{MetricValue}' for metric '{MetricName}' with aggregation interval '{AggregationInterval}'", foundMetricValue, metricDefinition.Name, aggregationInterval);

var metricsTimestampFeatureFlag = FeatureFlag.IsActive("METRICSTIMESTAMP", defaultFlagState: true);

var gauge = Metrics.CreateGauge(metricDefinition.Name, metricDefinition.Description, includeTimestamp: metricsTimestampFeatureFlag);
gauge.Set(foundMetricValue);
}
catch (Exception exception)
{
_exceptionTracker.Track(exception);
}
}

private TimeSpan DetermineMetricAggregationInterval(MetricDefinition metricDefinition)
{
Guard.NotNull(metricDefinition, nameof(metricDefinition));

if (metricDefinition?.AzureMetricConfiguration?.Aggregation?.Interval != null)
{
return metricDefinition.AzureMetricConfiguration.Aggregation.Interval.Value;
}

if (MetricDefaults.Aggregation.Interval == null)
{
throw new Exception($"No default aggregation interval is configured nor on the metric configuration for '{metricDefinition?.Name}'");
}

return MetricDefaults.Aggregation.Interval.Value;
}

/// <summary>
/// Scrapes the configured resource
/// </summary>
/// <param name="subscriptionId">Metric subscription Id</param>
/// <param name="resourceGroupName">Metric Resource Group</param>
/// <param name="metricDefinition">Definition of the metric to scrape</param>
/// <param name="aggregationType">Aggregation for the metric to use</param>
/// <param name="aggregationInterval">Interval that is used to aggregate metrics</param>
///
protected abstract Task<double> ScrapeResourceAsync(string subscriptionId, string resourceGroupName, TMetricDefinition metricDefinition, AggregationType aggregationType, TimeSpan aggregationInterval);
}
}
Loading