Skip to content

Commit

Permalink
MSSQL Integration: Adds query_config_path to allow for custom metrics…
Browse files Browse the repository at this point in the history
… through custom exporter config file (#5768)

* Adds query_config_path to mssql integration to allow for custom metrics through custom exporter config file

* Updates query_config_path to agent flow and fixes tests/docs

* Adds both query_config and query_config_file to mssql integration

* Removes query_config_file from mssql config params
  • Loading branch information
StefanKurek authored Nov 27, 2023
1 parent dcfdce5 commit 7f8bb76
Show file tree
Hide file tree
Showing 8 changed files with 721 additions and 36 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ v0.38.0 (2023-11-21)
- Make component list sortable in web UI. (@hainenber)

- Adds new metrics (`mssql_server_total_memory_bytes`, `mssql_server_target_memory_bytes`,
and `mssql_available_commit_memory_bytes`) for `mssql` integration.
and `mssql_available_commit_memory_bytes`) for `mssql` integration (@StefanKurek).

- Grafana Agent Operator: `config-reloader` container no longer runs as root.
(@rootmout)
Expand All @@ -150,6 +150,8 @@ v0.38.0 (2023-11-21)

- Allow agent to start with `module.git` config if cached before. (@hainenber)

- Adds new optional config parameter `query_config` to `mssql` integration to allow for custom metrics (@StefanKurek)

### Bugfixes

- Set exit code 1 on grafana-agentctl non-runnable command. (@fgouteroux)
Expand Down
21 changes: 17 additions & 4 deletions component/prometheus/exporter/mssql/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ package mssql

import (
"errors"
"fmt"
"time"

"github.com/burningalchemist/sql_exporter/config"
"github.com/grafana/agent/component"
"github.com/grafana/agent/component/prometheus/exporter"
"github.com/grafana/agent/pkg/integrations"
"github.com/grafana/agent/pkg/integrations/mssql"
"github.com/grafana/agent/pkg/util"
"github.com/grafana/river/rivertypes"
config_util "github.com/prometheus/common/config"
"gopkg.in/yaml.v2"
)

func init() {
Expand All @@ -36,10 +40,11 @@ var DefaultArguments = Arguments{

// Arguments controls the mssql exporter.
type Arguments struct {
ConnectionString rivertypes.Secret `river:"connection_string,attr"`
MaxIdleConnections int `river:"max_idle_connections,attr,optional"`
MaxOpenConnections int `river:"max_open_connections,attr,optional"`
Timeout time.Duration `river:"timeout,attr,optional"`
ConnectionString rivertypes.Secret `river:"connection_string,attr"`
MaxIdleConnections int `river:"max_idle_connections,attr,optional"`
MaxOpenConnections int `river:"max_open_connections,attr,optional"`
Timeout time.Duration `river:"timeout,attr,optional"`
QueryConfig rivertypes.OptionalSecret `river:"query_config,attr,optional"`
}

// SetToDefault implements river.Defaulter.
Expand All @@ -60,6 +65,13 @@ func (a *Arguments) Validate() error {
if a.Timeout <= 0 {
return errors.New("timeout must be positive")
}

var collectorConfig config.CollectorConfig
err := yaml.UnmarshalStrict([]byte(a.QueryConfig.Value), &collectorConfig)
if err != nil {
return fmt.Errorf("invalid query_config: %s", err)
}

return nil
}

Expand All @@ -69,5 +81,6 @@ func (a *Arguments) Convert() *mssql.Config {
MaxIdleConnections: a.MaxIdleConnections,
MaxOpenConnections: a.MaxOpenConnections,
Timeout: a.Timeout,
QueryConfig: util.RawYAML(a.QueryConfig.Value),
}
}
126 changes: 115 additions & 11 deletions component/prometheus/exporter/mssql/mssql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import (
"testing"
"time"

"github.com/burningalchemist/sql_exporter/config"
"github.com/grafana/agent/pkg/integrations/mssql"
"github.com/grafana/river"
"github.com/grafana/river/rivertypes"
config_util "github.com/prometheus/common/config"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)

func TestRiverUnmarshal(t *testing.T) {
riverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
max_idle_connections = 3
max_open_connections = 3
timeout = "10s"
`
timeout = "10s"`

var args Arguments
err := river.Unmarshal([]byte(riverConfig), &args)
Expand All @@ -33,6 +34,64 @@ func TestRiverUnmarshal(t *testing.T) {
require.Equal(t, expected, args)
}

func TestRiverUnmarshalWithInlineQueryConfig(t *testing.T) {
riverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
max_idle_connections = 3
max_open_connections = 3
timeout = "10s"
query_config = "{ collector_name: mssql_standard, metrics: [ { metric_name: mssql_local_time_seconds, type: gauge, help: 'Local time in seconds since epoch (Unix time).', values: [ unix_time ], query: \"SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time\" } ] }"`

var args Arguments
err := river.Unmarshal([]byte(riverConfig), &args)
require.NoError(t, err)
var collectorConfig config.CollectorConfig
err = yaml.UnmarshalStrict([]byte(args.QueryConfig.Value), &collectorConfig)
require.NoError(t, err)

require.Equal(t, rivertypes.Secret("sqlserver://user:pass@localhost:1433"), args.ConnectionString)
require.Equal(t, 3, args.MaxIdleConnections)
require.Equal(t, 3, args.MaxOpenConnections)
require.Equal(t, 10*time.Second, args.Timeout)
require.Equal(t, "mssql_standard", collectorConfig.Name)
require.Equal(t, 1, len(collectorConfig.Metrics))
require.Equal(t, "mssql_local_time_seconds", collectorConfig.Metrics[0].Name)
require.Equal(t, "gauge", collectorConfig.Metrics[0].TypeString)
require.Equal(t, "Local time in seconds since epoch (Unix time).", collectorConfig.Metrics[0].Help)
require.Equal(t, 1, len(collectorConfig.Metrics[0].Values))
require.Contains(t, collectorConfig.Metrics[0].Values, "unix_time")
require.Equal(t, "SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time", collectorConfig.Metrics[0].QueryLiteral)
}

func TestRiverUnmarshalWithInlineQueryConfigYaml(t *testing.T) {
riverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
max_idle_connections = 3
max_open_connections = 3
timeout = "10s"
query_config = "collector_name: mssql_standard\nmetrics:\n- metric_name: mssql_local_time_seconds\n type: gauge\n help: 'Local time in seconds since epoch (Unix time).'\n values: [unix_time]\n query: \"SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time\""`

var args Arguments
err := river.Unmarshal([]byte(riverConfig), &args)
require.NoError(t, err)
var collectorConfig config.CollectorConfig
err = yaml.UnmarshalStrict([]byte(args.QueryConfig.Value), &collectorConfig)
require.NoError(t, err)

require.Equal(t, rivertypes.Secret("sqlserver://user:pass@localhost:1433"), args.ConnectionString)
require.Equal(t, 3, args.MaxIdleConnections)
require.Equal(t, 3, args.MaxOpenConnections)
require.Equal(t, 10*time.Second, args.Timeout)
require.Equal(t, "mssql_standard", collectorConfig.Name)
require.Equal(t, 1, len(collectorConfig.Metrics))
require.Equal(t, "mssql_local_time_seconds", collectorConfig.Metrics[0].Name)
require.Equal(t, "gauge", collectorConfig.Metrics[0].TypeString)
require.Equal(t, "Local time in seconds since epoch (Unix time).", collectorConfig.Metrics[0].Help)
require.Equal(t, 1, len(collectorConfig.Metrics[0].Values))
require.Contains(t, collectorConfig.Metrics[0].Values, "unix_time")
require.Equal(t, "SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time", collectorConfig.Metrics[0].QueryLiteral)
}

func TestUnmarshalInvalid(t *testing.T) {
invalidRiverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
Expand All @@ -44,6 +103,37 @@ func TestUnmarshalInvalid(t *testing.T) {
var invalidArgs Arguments
err := river.Unmarshal([]byte(invalidRiverConfig), &invalidArgs)
require.Error(t, err)
require.EqualError(t, err, "timeout must be positive")
}

func TestUnmarshalInvalidQueryConfigYaml(t *testing.T) {
invalidRiverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
max_idle_connections = 1
max_open_connections = 1
timeout = "1s"
query_config = "{ collector_name: mssql_standard, metrics: [ { metric_name: mssql_local_time_seconds, type: gauge, help: 'Local time in seconds since epoch (Unix time).', values: [ unix_time ], query: \"SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time\" }"
`

var invalidArgs Arguments
err := river.Unmarshal([]byte(invalidRiverConfig), &invalidArgs)
require.Error(t, err)
require.EqualError(t, err, "invalid query_config: yaml: line 1: did not find expected ',' or ']'")
}

func TestUnmarshalInvalidProperty(t *testing.T) {
invalidRiverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
max_idle_connections = 1
max_open_connections = 1
timeout = "1s"
query_config = "collector_name: mssql_standard\nbad_param: true\nmetrics:\n- metric_name: mssql_local_time_seconds\n type: gauge\n help: 'Local time in seconds since epoch (Unix time).'\n values: [unix_time]\n query: \"SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time\""
`

var invalidArgs Arguments
err := river.Unmarshal([]byte(invalidRiverConfig), &invalidArgs)
require.Error(t, err)
require.EqualError(t, err, "invalid query_config: unknown fields in collector: bad_param")
}

func TestArgumentsValidate(t *testing.T) {
Expand Down Expand Up @@ -89,6 +179,9 @@ func TestArgumentsValidate(t *testing.T) {
MaxIdleConnections: 1,
MaxOpenConnections: 1,
Timeout: 10 * time.Second,
QueryConfig: rivertypes.OptionalSecret{
Value: `{ collector_name: mssql_standard, metrics: [ { metric_name: mssql_local_time_seconds, type: gauge, help: 'Local time in seconds since epoch (Unix time).', values: [ unix_time ], query: "SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time" } ] }`,
},
},
wantErr: false,
},
Expand All @@ -107,20 +200,31 @@ func TestArgumentsValidate(t *testing.T) {
}

func TestConvert(t *testing.T) {
riverConfig := `
connection_string = "sqlserver://user:pass@localhost:1433"
`
var args Arguments
err := river.Unmarshal([]byte(riverConfig), &args)
require.NoError(t, err)
strQueryConfig := `collector_name: mssql_standard
metrics:
- metric_name: mssql_local_time_seconds
type: gauge
help: 'Local time in seconds since epoch (Unix time).'
values: [unix_time]
query: "SELECT DATEDIFF(second, '19700101', GETUTCDATE()) AS unix_time"`

args := Arguments{
ConnectionString: rivertypes.Secret("sqlserver://user:pass@localhost:1433"),
MaxIdleConnections: 1,
MaxOpenConnections: 1,
Timeout: 10 * time.Second,
QueryConfig: rivertypes.OptionalSecret{
Value: strQueryConfig,
},
}
res := args.Convert()

expected := mssql.Config{
ConnectionString: config_util.Secret("sqlserver://user:pass@localhost:1433"),
MaxIdleConnections: DefaultArguments.MaxIdleConnections,
MaxOpenConnections: DefaultArguments.MaxOpenConnections,
Timeout: DefaultArguments.Timeout,
MaxIdleConnections: 1,
MaxOpenConnections: 1,
Timeout: 10 * time.Second,
QueryConfig: []byte(strQueryConfig),
}
require.Equal(t, expected, *res)
}
Loading

0 comments on commit 7f8bb76

Please sign in to comment.