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

MSSQL Integration: Adds query_config_path to allow for custom metrics through custom exporter config file #5768

Merged
merged 4 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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