Skip to content

Commit

Permalink
Provide support for scraping metrics for Azure SQL Managed Insta… (#811)
Browse files Browse the repository at this point in the history
* Update docs & changelog

Signed-off-by: Tom Kerkhove <[email protected]>

* Update new scraper guide

Signed-off-by: Tom Kerkhove <[email protected]>

* Implement SQL Managed Instance scraper

Signed-off-by: Tom Kerkhove <[email protected]>
  • Loading branch information
tomkerkhove authored Jan 6, 2020
1 parent 6c390c9 commit 66bc1c6
Show file tree
Hide file tree
Showing 18 changed files with 344 additions and 5 deletions.
14 changes: 11 additions & 3 deletions adding-a-new-scraper.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ discuss your scenario_

## Configuration

<!-- markdownlint-disable MD013 -->
1. Add your new scraping type to the `Promitor.Core.Scraping.Configuration.Model.ResourceType`.
2. Describe the resource for which you're scraping metrics by creating `<New-Type>ResourceDefinition`
and inherit from
`Promitor.Core.Scraping.Configuration.Model.Metrics.AzureResourceDefinition` -
this class should go in `.\src\Promitor.Core.Scraping\Configuration\Model\Metrics\ResourceTypes`.
3. Create a new Deserializer in `.\src\Promitor.Core.Scraping\Configuration\Serialization\v1\Providers`.
3. Describe the resource configurationh for which you're scraping metrics by creating
`<New-Type>ResourceV1`
and inherit from
`Promitor.Core.Scraping.Configuration.Serialization.v1.Model.AzureResourceDefinitionV1` -
this class should go in `.\src\Promitor.Core.Scraping\Configuration\Serialization\v1\Model\ResourceTypes`.
4. Create a new Deserializer in `.\src\Promitor.Core.Scraping\Configuration\Serialization\v1\Providers`.
This must inherit from `ResourceDeserializer`.
4. Update `Promitor.Core.Scraping.Configuration.v1.Core.AzureResourceDeserializerFactory`
5. Update `Promitor.Core.Scraping.Configuration.v1.Core.AzureResourceDeserializerFactory`
to handle your new resource type by returning a new instance of the Deserializer
you created in the previous step.
5. Provide a unit test in `.\src\Promitor.Scraper.Tests.Unit\Serialization\v1\Providers`
6. Provide a unit test in `.\src\Promitor.Scraper.Tests.Unit\Serialization\v1\Providers`
that tests the deserialization based on our sample. Your test class must inherit
from `ResourceDeserializerTest` to ensure the inherited functionality is tested.

Expand Down Expand Up @@ -50,6 +56,8 @@ This requires the following steps:
2. Hook your new scraper in our `MetricScraperFactory` which determines what scraper
to use for the passed configuration.

<!-- markdownlint-enable -->

------------------------

:memo: _Currently we don't have integration tests_
Expand Down
1 change: 1 addition & 0 deletions changelog/content/experimental/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version:

- {{% tag added %}} Multi-dimensional metric support ([docs](https://promitor.io/configuration/v1.x/metrics/#metrics) | [#81](https://github.com/tomkerkhove/promitor/issues/81))
- {{% tag added %}} Azure SQL Database Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/sql-database) | [#317](https://github.com/tomkerkhove/promitor/issues/317))
- {{% tag added %}} Azure SQL Managed Instance Scraper ([docs](https://promitor.io/configuration/v1.x/metrics/sql-managed-instance) | [#381](https://github.com/tomkerkhove/promitor/issues/381))
- {{% tag added %}} OpenAPI v3.0 support (`/api/v1/docs.json` | [docs](ttps://promitor.io/operations/#exploring-our-rest-apis) | [#734](https://github.com/tomkerkhove/promitor/issues/734))
- {{% tag added %}} OpenAPI UI based on OpenAPI v3.0 (`/api/docs/` | [docs](ttps://promitor.io/operations/#exploring-our-rest-apis) | [#734](https://github.com/tomkerkhove/promitor/issues/734))
- {{% tag added %}} Provide traces in Azure Application Insights ([docs](https://promitor.io/configuration/v1.x/runtime#azure-application-insights) | [#29](https://github.com/tomkerkhove/promitor/issues/29))
Expand Down
1 change: 1 addition & 0 deletions docs/configuration/v1.x/metrics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ We also provide a simplified way to scrape the following Azure resources:
- [Azure Network Interface](network-interface)
- [Azure Service Bus Queue](service-bus-queue)
- [Azure SQL Database](sql-database)
- [Azure SQL Managed Instance](sql-managed-instance)
- [Azure Storage Queue](storage-queue)
- [Azure Virtual Machine](virtual-machine)
Expand Down
34 changes: 34 additions & 0 deletions docs/configuration/v1.x/metrics/sql-managed-instance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
layout: default
title: Azure SQL Managed Instance Declaration
---

## Azure SQL Managed Instance - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.1-green.svg)

You can scrape an Azure SQL Managed Instance via the `SqlManagedInstance`
resource type.

The following fields need to be provided:

- `instanceName` - The name of the SQL Server instance.

All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftsqlmanagedinstances).

Example:

```yaml
name: promitor_demo_azuresqlmanagedinstance_nodes
description: "The amount of nodes for an Azure SQL Managed Instance."
resourceType: SqlManagedInstance
azureMetricConfiguration:
metricName: virtual_core_count
aggregation:
type: Average
resources:
- instanceName: promitor-sql-managed-instance
```
<!-- markdownlint-disable MD033 -->
[&larr; back to metrics declarations](/configuration/v1.x/metrics)<br />
[&larr; back to introduction](/)
<!-- markdownlint-enable -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes
{
/// <summary>
/// Represents an Azure SQL Managed Instance resource.
/// </summary>
public class SqlManagedInstanceDefinition : AzureResourceDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="SqlManagedInstanceDefinition" /> class.
/// </summary>
/// <param name="resourceGroupName">The name of the resource group the server is in.</param>
/// <param name="instanceName">The name of the Azure SQL Managed Instance resource.</param>
public SqlManagedInstanceDefinition(string resourceGroupName, string instanceName)
: base(ResourceType.SqlManagedInstance, resourceGroupName)
{
InstanceName = instanceName;
}

/// <summary>
/// The name of the Azure SQL Managed Instance resource.
/// </summary>
public string InstanceName { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum ResourceType
CosmosDb = 8,
RedisCache = 9,
PostgreSql = 10,
SqlDatabase = 11
SqlDatabase = 11,
SqlManagedInstance = 12
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public IDeserializer<AzureResourceDefinitionV1> GetDeserializerFor(ResourceType
case ResourceType.SqlDatabase:
var sqlDatabaseLogger = _loggerFactory.CreateLogger<SqlDatabaseDeserializer>();
return new SqlDatabaseDeserializer(sqlDatabaseLogger);
case ResourceType.SqlManagedInstance:
var sqlManagedInstanceLogger = _loggerFactory.CreateLogger<SqlManagedInstanceDeserializer>();
return new SqlManagedInstanceDeserializer(sqlManagedInstanceLogger);
default:
throw new ArgumentOutOfRangeException($"Resource Type {resourceType} not supported.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public V1MappingProfile()
CreateMap<StorageQueueResourceV1, StorageQueueResourceDefinition>();
CreateMap<VirtualMachineResourceV1, VirtualMachineResourceDefinition>();
CreateMap<SqlDatabaseResourceV1, SqlDatabaseResourceDefinition>();
CreateMap<SqlManagedInstanceResourceV1, SqlManagedInstanceDefinition>();

CreateMap<MetricDefinitionV1, PrometheusMetricDefinition>();

Expand All @@ -50,7 +51,8 @@ public V1MappingProfile()
.Include<ServiceBusQueueResourceV1, ServiceBusQueueResourceDefinition>()
.Include<StorageQueueResourceV1, StorageQueueResourceDefinition>()
.Include<VirtualMachineResourceV1, VirtualMachineResourceDefinition>()
.Include<SqlDatabaseResourceV1, SqlDatabaseResourceDefinition>();
.Include<SqlDatabaseResourceV1, SqlDatabaseResourceDefinition>()
.Include<SqlManagedInstanceResourceV1, SqlManagedInstanceDefinition>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes
{
/// <summary>
/// Represents an Azure SQL Managed Instance to scrape.
/// </summary>
public class SqlManagedInstanceResourceV1 : AzureResourceDefinitionV1
{
/// <summary>
/// The name of the Azure SQL Managed Instance resource.
/// </summary>
public string InstanceName { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.Extensions.Logging;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Model;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes;
using YamlDotNet.RepresentationModel;

namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers
{
/// <summary>
/// Used to deserialize a <see cref="SqlManagedInstanceDeserializer" /> resource.
/// </summary>
public class SqlManagedInstanceDeserializer : ResourceDeserializer
{
/// <summary>
/// Initializes a new instance of the <see cref="SqlManagedInstanceDeserializer" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
public SqlManagedInstanceDeserializer(ILogger logger) : base(logger)
{
}

protected override AzureResourceDefinitionV1 DeserializeResource(YamlMappingNode node)
{
var instanceName = node.GetString("instanceName");

return new SqlManagedInstanceResourceV1
{
InstanceName = instanceName
};
}
}
}
2 changes: 2 additions & 0 deletions src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public IScraper<AzureResourceDefinition> CreateScraper(ResourceType metricDefini
return new PostgreSqlScraper(scraperConfiguration);
case ResourceType.SqlDatabase:
return new SqlDatabaseScraper(scraperConfiguration);
case ResourceType.SqlManagedInstance:
return new SqlManagedInstanceScraper(scraperConfiguration);
default:
throw new ArgumentOutOfRangeException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Management.Monitor.Fluent.Models;
using Promitor.Core.Scraping.Configuration.Model.Metrics;
using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes;

namespace Promitor.Core.Scraping.ResourceTypes
{
public class SqlManagedInstanceScraper : Scraper<SqlManagedInstanceDefinition>
{
private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/managedInstances/{2}";

/// <summary>
/// Initializes an instance of the <see cref="SqlManagedInstanceScraper" /> class.
/// </summary>
/// <param name="scraperConfiguration">The scraper configuration</param>
public SqlManagedInstanceScraper(ScraperConfiguration scraperConfiguration) : base(scraperConfiguration)
{
}

protected override async Task<ScrapeResult> ScrapeResourceAsync(string subscriptionId, ScrapeDefinition<AzureResourceDefinition> scrapeDefinition, SqlManagedInstanceDefinition resourceDefinition, AggregationType aggregationType, TimeSpan aggregationInterval)
{
var resourceUri = string.Format(
ResourceUriTemplate,
AzureMetadata.SubscriptionId,
scrapeDefinition.ResourceGroupName,
resourceDefinition.InstanceName);

var metricName = scrapeDefinition.AzureMetricConfiguration.MetricName;
var dimensionName = scrapeDefinition.AzureMetricConfiguration.Dimension?.Name;
var foundMetricValue = await AzureMonitorClient.QueryMetricAsync(metricName, dimensionName, aggregationType, aggregationInterval, resourceUri);

return new ScrapeResult(subscriptionId, scrapeDefinition.ResourceGroupName, null, resourceUri, foundMetricValue);
}
}
}
8 changes: 8 additions & 0 deletions src/Promitor.Scraper.Host/Docs/Open-Api.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal static IMetricValidator GetValidatorFor(ResourceType resourceType)
return new PostgreSqlMetricValidator();
case ResourceType.SqlDatabase:
return new SqlDatabaseMetricValidator();
case ResourceType.SqlManagedInstance:
return new SqlManagedInstanceMetricValidator();
}

throw new ArgumentOutOfRangeException(nameof(resourceType), $"No validation rules are defined for metric type '{resourceType}'");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using GuardNet;
using Promitor.Core.Scraping.Configuration.Model.Metrics;
using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes;
using Promitor.Scraper.Host.Validation.MetricDefinitions.Interfaces;

namespace Promitor.Scraper.Host.Validation.MetricDefinitions.ResourceTypes
{
/// <summary>
/// Validates <see cref="SqlManagedInstanceMetricValidator" /> objects.
/// </summary>
public class SqlManagedInstanceMetricValidator : IMetricValidator
{
/// <inheritdoc />
public IEnumerable<string> Validate(MetricDefinition metricDefinition)
{
Guard.NotNull(metricDefinition, nameof(metricDefinition));

foreach (var definition in metricDefinition.Resources.Cast<SqlManagedInstanceDefinition>())
{
if (string.IsNullOrWhiteSpace(definition.InstanceName))
{
yield return "No instance name is configured";
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -351,5 +351,31 @@ public MetricsDeclarationBuilder WithSqlDatabaseMetric(

return this;
}

public MetricsDeclarationBuilder WithSqlManagedInstanceMetric(
string metricName = "promitor-sql-managed-instance",
string azureMetricName = "cpu_percent",
string instanceName = "promitor-sql-instance",
string metricDescription = "Metric description")
{
var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName);
var resource = new SqlManagedInstanceResourceV1
{
InstanceName = instanceName
};

var metric = new MetricDefinitionV1
{
Name = metricName,
Description = metricDescription,
AzureMetricConfiguration = azureMetricConfiguration,
Resources = new List<AzureResourceDefinitionV1> { resource },
ResourceType = ResourceType.SqlManagedInstance
};

_metrics.Add(metric);

return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.ComponentModel;
using Microsoft.Extensions.Logging.Abstractions;
using Promitor.Core.Scraping.Configuration.Serialization;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Model;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes;
using Promitor.Core.Scraping.Configuration.Serialization.v1.Providers;
using Xunit;

namespace Promitor.Scraper.Tests.Unit.Serialization.v1.Providers
{
[Category("Unit")]
public class SqlManagedInstanceDeserializerTests : ResourceDeserializerTest<SqlManagedInstanceDeserializer>
{
private readonly SqlManagedInstanceDeserializer _deserializer;

public SqlManagedInstanceDeserializerTests()
{
_deserializer = new SqlManagedInstanceDeserializer(NullLogger.Instance);
}

[Fact]
public void Deserialize_InstanceNameSupplied_SetsDatabaseName()
{
YamlAssert.PropertySet<SqlManagedInstanceResourceV1, AzureResourceDefinitionV1, string>(
_deserializer,
"instanceName: promitor-instance",
"promitor-instance",
c => c.InstanceName);
}

[Fact]
public void Deserialize_InstanceNameNotSupplied_Null()
{
YamlAssert.PropertyNull<SqlManagedInstanceResourceV1, AzureResourceDefinitionV1>(
_deserializer,
"resourceGroupName: promitor-group",
c => c.InstanceName);
}

protected override IDeserializer<AzureResourceDefinitionV1> CreateDeserializer()
{
return new SqlDatabaseDeserializer(NullLogger.Instance);
}
}
}
Loading

0 comments on commit 66bc1c6

Please sign in to comment.