Skip to content

Commit 82aa8df

Browse files
authored
feat: add support for monitoring azure sql managed instance (#236)
feat: add support for monitoring azure sql managed instance (#236)
1 parent fbce4a5 commit 82aa8df

16 files changed

Lines changed: 682 additions & 134 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Unreleased section should follow [Release Toolkit](https://github.com/newrelic/r
1111

1212
### enhancement
1313
- Add support for monitoring Azure SQL Database
14+
- Add support for monitoring Azure SQL Managed Instance
1415

1516
## v2.18.2 - 2025-06-03
1617

src/database/sql_database.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import (
1212
"github.com/newrelic/nri-mssql/src/connection"
1313
)
1414

15-
// databaseNameQuery gets all database names
16-
const databaseNameQuery = "select name as db_name from sys.databases where name not in ('master', 'tempdb', 'msdb', 'model', 'rdsadmin', 'distribution', 'model_msdb', 'model_replicatedmaster')"
17-
const engineEditionQuery = "SELECT SERVERPROPERTY('EngineEdition') AS EngineEdition;"
18-
const AzureSQLDatabaseEngineEditionNumber = 5
15+
const (
16+
// databaseNameQuery gets all database names
17+
databaseNameQuery = "select name as db_name from sys.databases where name not in ('master', 'tempdb', 'msdb', 'model', 'rdsadmin', 'distribution', 'model_msdb', 'model_replicatedmaster')"
18+
engineEditionQuery = "SELECT SERVERPROPERTY('EngineEdition') AS EngineEdition;"
19+
AzureSQLDatabaseEngineEditionNumber = 5
20+
AzureSQLManagedInstanceEngineEditionNumber = 8
21+
)
1922

2023
// NameRow is a row result in the databaseNameQuery
2124
type NameRow struct {
@@ -127,6 +130,7 @@ func GetEngineEdition(connection *connection.SQLConnection) (int, error) {
127130
log.Debug("EngineEdition query returned empty output.")
128131
return 0, nil
129132
} else {
133+
log.Debug("Detected EngineEdition: %d", engineEdition[0])
130134
return engineEdition[0], nil
131135
}
132136
}

src/database/sql_database_test.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"gopkg.in/DATA-DOG/go-sqlmock.v1"
1414
)
1515

16-
const azureSQLManagedInstanceEngineEditionNumber = 8
17-
1816
func Test_createDatabaseEntities_QueryError(t *testing.T) {
1917
i, err := integration.New("test", "1.0.0")
2018
if err != nil {
@@ -206,10 +204,10 @@ func TestGetEngineEdition(t *testing.T) {
206204
name: "Successful query - Other SQL Server",
207205
setupMock: func(mock sqlmock.Sqlmock) {
208206
expectedRows := sqlmock.NewRows([]string{"EngineEdition"}).
209-
AddRow(azureSQLManagedInstanceEngineEditionNumber)
207+
AddRow(AzureSQLManagedInstanceEngineEditionNumber)
210208
mock.ExpectQuery("SELECT (.+)").WillReturnRows(expectedRows)
211209
},
212-
expectedEdition: azureSQLManagedInstanceEngineEditionNumber,
210+
expectedEdition: AzureSQLManagedInstanceEngineEditionNumber,
213211
expectError: false,
214212
},
215213
{
@@ -260,7 +258,7 @@ func TestIsAzureSQLDatabase(t *testing.T) {
260258
},
261259
{
262260
name: "Azure SQL Managed Instance",
263-
engineEdition: azureSQLManagedInstanceEngineEditionNumber,
261+
engineEdition: AzureSQLManagedInstanceEngineEditionNumber,
264262
expected: false,
265263
},
266264
}

src/inventory/inventory.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ func setItemOrLog(instanceEntity *integration.Entity, key string, value interfac
8181

8282
// Bucket for processor functions
8383
var spConfigProcessorFunctionSet = metrics.EngineSet[spConfigItemsProcessor]{
84-
Default: processSPConfigItems,
85-
AzureSQLDatabase: processAzureSQLDatabaseSPConfigItems,
84+
Default: processSPConfigItems,
85+
AzureSQLDatabase: processAzureSQLDatabaseSPConfigItems,
86+
AzureSQLManagedInstance: processSPConfigItems,
8687
}
8788

8889
func processSPConfigItems(instanceEntity *integration.Entity, connection *connection.SQLConnection) error {

src/metrics/database_metric_definitions.go

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"strings"
66

7-
"github.com/newrelic/infra-integrations-sdk/v3/log"
87
"github.com/newrelic/nri-mssql/src/database"
98
)
109

@@ -103,6 +102,41 @@ var databaseDefinitionsForAzureSQLDatabase = []*QueryDefinition{
103102
},
104103
}
105104

105+
var databaseDefinitionsForAzureSQLManagedInstance = []*QueryDefinition{
106+
{
107+
query: `
108+
SELECT
109+
sd.name AS db_name,
110+
spc.cntr_value AS log_growth
111+
FROM sys.dm_os_performance_counters spc WITH (NOLOCK)
112+
INNER JOIN sys.databases sd
113+
ON sd.physical_database_name = spc.instance_name
114+
WHERE spc.object_name LIKE '%:Databases%'
115+
AND spc.counter_name = 'Log Growths'
116+
AND sd.name NOT IN ('master', 'tempdb', 'msdb', 'model', 'rdsadmin', 'distribution', 'model_msdb', 'model_replicatedmaster')
117+
AND spc.instance_name NOT IN ('_Total', 'mssqlsystemresource', 'master', 'tempdb', 'msdb', 'model', 'rdsadmin', 'distribution', 'model_msdb', 'model_replicatedmaster')
118+
`,
119+
dataModels: &[]struct {
120+
database.DataModel
121+
LogGrowth int `db:"log_growth" metric_name:"log.transactionGrowth" source_type:"gauge"`
122+
}{},
123+
},
124+
{
125+
query: `
126+
SELECT
127+
DB_NAME(database_id) AS db_name,
128+
SUM(io_stall) AS io_stalls
129+
FROM sys.dm_io_virtual_file_stats(null,null)
130+
WHERE DB_NAME(database_id) NOT IN ('master', 'tempdb', 'msdb', 'model', 'rdsadmin', 'distribution', 'model_msdb', 'model_replicatedmaster')
131+
GROUP BY database_id
132+
`,
133+
dataModels: &[]struct {
134+
database.DataModel
135+
IOStalls int `db:"io_stalls" metric_name:"io.stallInMilliseconds" source_type:"gauge"`
136+
}{},
137+
},
138+
}
139+
106140
var databaseDiskDefinitionsForAzureSQLDatabase = []*QueryDefinition{
107141
{
108142
query: `
@@ -193,52 +227,3 @@ var specificDatabaseDefinitionsForAzureSQLDatabase = []*QueryDefinition{
193227
}{},
194228
},
195229
}
196-
197-
// EngineSet is a generic struct that acts as a "bucket" for holding
198-
// the default and Azure-specific implementations for a given resource.
199-
type EngineSet[T any] struct {
200-
Default T
201-
AzureSQLDatabase T
202-
}
203-
204-
// Select returns the correct implementation from the set based on the engine edition.
205-
func (s EngineSet[T]) Select(engineEdition int) T {
206-
if engineEdition == database.AzureSQLDatabaseEngineEditionNumber {
207-
return s.AzureSQLDatabase
208-
}
209-
return s.Default
210-
}
211-
212-
// QueryDefinitionType is a custom type for identifying different query sets.
213-
type QueryDefinitionType int
214-
215-
// Enum of the different query definition types.
216-
const (
217-
StandardQueries = iota
218-
BufferQueries
219-
SpecificQueries
220-
)
221-
222-
var queryDefinitionSets = map[QueryDefinitionType]EngineSet[[]*QueryDefinition]{
223-
StandardQueries: {
224-
Default: databaseDefinitions,
225-
AzureSQLDatabase: databaseDefinitionsForAzureSQLDatabase,
226-
},
227-
BufferQueries: {
228-
Default: databaseBufferDefinitions,
229-
AzureSQLDatabase: databaseBufferDefinitionsForAzureSQLDatabase,
230-
},
231-
SpecificQueries: {
232-
Default: specificDatabaseDefinitions,
233-
AzureSQLDatabase: specificDatabaseDefinitionsForAzureSQLDatabase,
234-
},
235-
}
236-
237-
func GetQueryDefinitions(defType QueryDefinitionType, engineEdition int) []*QueryDefinition {
238-
definitionSet, ok := queryDefinitionSets[defType]
239-
if !ok {
240-
log.Error("Error: Invalid query definition type provided: %d", defType)
241-
return nil
242-
}
243-
return definitionSet.Select(engineEdition)
244-
}

src/metrics/instance_metric_definitions.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ var instanceDefinitions = []*QueryDefinition{
122122
InstanceActiveConnections *int64 `db:"instance_active_connections" metric_name:"activeConnections" source_type:"gauge"`
123123
}{},
124124
},
125+
}
126+
127+
var instanceMemoryDefinitions = []*QueryDefinition{
125128
{
126129
query: `SELECT
127130
Max(sys_mem.total_physical_memory_kb * 1024.0) AS total_physical_memory,
@@ -138,6 +141,25 @@ var instanceDefinitions = []*QueryDefinition{
138141
},
139142
}
140143

144+
var instanceMemoryDefinitionsForAzureSQLManagedInstance = []*QueryDefinition{
145+
{
146+
query: `
147+
SELECT
148+
Max(sys_mem.total_physical_memory_kb * 1024.0) AS total_physical_memory,
149+
Max(sys_mem.available_physical_memory_kb * 1024.0) AS available_physical_memory,
150+
(Max(proc_mem.physical_memory_in_use_kb) / (Max(sys_mem.total_physical_memory_kb) * 1.0)) * 100 AS memory_utilization
151+
FROM sys.dm_os_process_memory proc_mem,
152+
sys.dm_os_sys_memory sys_mem,
153+
sys.dm_os_performance_counters perf_count WHERE object_name LIKE '%:Memory Manager%'
154+
`,
155+
dataModels: &[]struct {
156+
TotalPhysicalMemory *float64 `db:"total_physical_memory" metric_name:"memoryTotal" source_type:"gauge"`
157+
AvailablePhysicalMemory *float64 `db:"available_physical_memory" metric_name:"memoryAvailable" source_type:"gauge"`
158+
MemoryUtilization *float64 `db:"memory_utilization" metric_name:"memoryUtilization" source_type:"gauge"`
159+
}{},
160+
},
161+
}
162+
141163
var instanceBufferDefinitions = []*QueryDefinition{
142164
{
143165
query: ` SELECT
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package metrics
2+
3+
import (
4+
"github.com/newrelic/infra-integrations-sdk/v3/log"
5+
"github.com/newrelic/nri-mssql/src/database"
6+
)
7+
8+
// EngineSet is a generic struct that acts as a "bucket" for holding
9+
// the default and Azure-specific implementations for a given resource.
10+
type EngineSet[T any] struct {
11+
Default T
12+
AzureSQLDatabase T
13+
AzureSQLManagedInstance T
14+
}
15+
16+
// Select returns the correct implementation from the set based on the engine edition.
17+
func (s EngineSet[T]) Select(engineEdition int) T {
18+
switch engineEdition {
19+
case database.AzureSQLDatabaseEngineEditionNumber:
20+
return s.AzureSQLDatabase
21+
case database.AzureSQLManagedInstanceEngineEditionNumber:
22+
return s.AzureSQLManagedInstance
23+
default:
24+
return s.Default
25+
}
26+
}
27+
28+
// QueryDefinitionType is a custom type for identifying different query sets.
29+
type QueryDefinitionType int
30+
31+
// Enum of the different query definition types.
32+
const (
33+
StandardQueries = iota
34+
BufferQueries
35+
SpecificQueries
36+
MemoryQueries
37+
)
38+
39+
var queryDefinitionSets = map[QueryDefinitionType]EngineSet[[]*QueryDefinition]{
40+
StandardQueries: {
41+
Default: databaseDefinitions,
42+
AzureSQLDatabase: databaseDefinitionsForAzureSQLDatabase,
43+
AzureSQLManagedInstance: databaseDefinitionsForAzureSQLManagedInstance,
44+
},
45+
BufferQueries: {
46+
Default: databaseBufferDefinitions,
47+
AzureSQLDatabase: databaseBufferDefinitionsForAzureSQLDatabase,
48+
AzureSQLManagedInstance: databaseBufferDefinitions,
49+
},
50+
SpecificQueries: {
51+
Default: specificDatabaseDefinitions,
52+
AzureSQLDatabase: specificDatabaseDefinitionsForAzureSQLDatabase,
53+
AzureSQLManagedInstance: specificDatabaseDefinitions,
54+
},
55+
MemoryQueries: {
56+
Default: instanceMemoryDefinitions,
57+
AzureSQLDatabase: []*QueryDefinition{},
58+
AzureSQLManagedInstance: instanceMemoryDefinitionsForAzureSQLManagedInstance,
59+
},
60+
}
61+
62+
func GetQueryDefinitions(defType QueryDefinitionType, engineEdition int) []*QueryDefinition {
63+
definitionSet, ok := queryDefinitionSets[defType]
64+
if !ok {
65+
log.Error("Error: Invalid query definition type provided: %d", defType)
66+
return nil
67+
}
68+
return definitionSet.Select(engineEdition)
69+
}

src/metrics/metrics.go

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func PopulateInstanceMetrics(instanceEntity *integration.Entity, connection *con
6161
)
6262

6363
collectionList := instanceDefinitions
64+
collectionList = append(collectionList, GetQueryDefinitions(MemoryQueries, engineEdition)...)
6465
if arguments.EnableBufferMetrics {
6566
collectionList = append(collectionList, instanceBufferDefinitions...)
6667
}
@@ -337,8 +338,9 @@ type databaseMetricsProcessor func(*integration.Integration, string, *connection
337338

338339
// Bucket for processor functions
339340
var processorFunctionSet = EngineSet[databaseMetricsProcessor]{
340-
Default: processDefaultDBMetrics,
341-
AzureSQLDatabase: processAzureSQLDatabaseMetrics,
341+
Default: processDefaultDBMetrics,
342+
AzureSQLDatabase: processAzureSQLDatabaseMetrics,
343+
AzureSQLManagedInstance: processDefaultDBMetrics,
342344
}
343345

344346
// PopulateDatabaseMetrics collects per-database metrics
@@ -369,9 +371,9 @@ func PopulateDatabaseMetrics(i *integration.Integration, instanceName string, co
369371
}
370372

371373
// processDefaultDBMetrics handles metric collection for a standard SQL Server instance.
372-
func processDefaultDBMetrics(i *integration.Integration, instanceName string, connection *connection.SQLConnection, arguments args.ArgumentList, dbSetLookup database.DBMetricSetLookup, _ int, modelChan chan<- interface{}) {
374+
func processDefaultDBMetrics(i *integration.Integration, instanceName string, connection *connection.SQLConnection, arguments args.ArgumentList, dbSetLookup database.DBMetricSetLookup, engineEdition int, modelChan chan<- interface{}) {
373375
// run queries that are not specific to a database
374-
processGeneralDBDefinitions(connection, modelChan)
376+
processDBDefinitions(connection, GetQueryDefinitions(StandardQueries, engineEdition), modelChan)
375377

376378
// run queries that are not specific to a database
377379
if arguments.EnableBufferMetrics {
@@ -384,6 +386,22 @@ func processDefaultDBMetrics(i *integration.Integration, instanceName string, co
384386
}
385387
}
386388

389+
// processAzureSQLDatabaseMetrics handles metric collection for Azure SQL Database concurrently.
390+
// It dispatches the work of processing each database to a worker goroutine.
391+
func processAzureSQLDatabaseMetrics(i *integration.Integration, instanceName string, _ *connection.SQLConnection, arguments args.ArgumentList, dbSetLookup database.DBMetricSetLookup, engineEdition int, modelChan chan<- interface{}) {
392+
databaseNames := dbSetLookup.GetDBNames()
393+
394+
dbChan := make(chan struct{}, maxConcurrentWorkers)
395+
var waitGroup sync.WaitGroup
396+
397+
for _, dbName := range databaseNames {
398+
waitGroup.Add(1)
399+
dbChan <- struct{}{}
400+
go processSingleAzureDB(&waitGroup, dbChan, dbName, arguments, engineEdition, modelChan)
401+
}
402+
waitGroup.Wait()
403+
}
404+
387405
func processSingleAzureDB(wg *sync.WaitGroup, dbChan chan struct{}, dbName string, arguments args.ArgumentList, engineEdition int, modelChan chan<- interface{}) {
388406
defer wg.Done()
389407
defer func() { <-dbChan }()
@@ -445,34 +463,12 @@ func processMemoryDBDefinitions(con *connection.SQLConnection, dbName string, mo
445463
}
446464
}
447465

448-
// processAzureSQLDatabaseMetrics handles metric collection for Azure SQL Database concurrently.
449-
// It dispatches the work of processing each database to a worker goroutine.
450-
func processAzureSQLDatabaseMetrics(i *integration.Integration, instanceName string, _ *connection.SQLConnection, arguments args.ArgumentList, dbSetLookup database.DBMetricSetLookup, engineEdition int, modelChan chan<- interface{}) {
451-
databaseNames := dbSetLookup.GetDBNames()
452-
453-
dbChan := make(chan struct{}, maxConcurrentWorkers)
454-
var waitGroup sync.WaitGroup
455-
456-
for _, dbName := range databaseNames {
457-
waitGroup.Add(1)
458-
dbChan <- struct{}{}
459-
go processSingleAzureDB(&waitGroup, dbChan, dbName, arguments, engineEdition, modelChan)
460-
}
461-
waitGroup.Wait()
462-
}
463-
464466
func processDBDefinitions(con *connection.SQLConnection, definitions []*QueryDefinition, modelChan chan<- interface{}) {
465467
for _, queryDef := range definitions {
466468
makeDBQuery(con, queryDef.GetQuery(), queryDef.GetDataModels(), modelChan)
467469
}
468470
}
469471

470-
func processGeneralDBDefinitions(con *connection.SQLConnection, modelChan chan<- interface{}) {
471-
for _, queryDef := range databaseDefinitions {
472-
makeDBQuery(con, queryDef.GetQuery(), queryDef.GetDataModels(), modelChan)
473-
}
474-
}
475-
476472
func processDBBufferDefinitions(con *connection.SQLConnection, modelChan chan<- interface{}) {
477473
for _, queryDef := range databaseBufferDefinitions {
478474
makeDBQuery(con, queryDef.GetQuery(), queryDef.GetDataModels(), modelChan)

0 commit comments

Comments
 (0)