@cryptotaxi247 / netdata-1 / commits / f800ddcb1

refactor(go.d.plugin): use nested functions config for database collectors (#21655)

Ilya Mashchenko committed Jan 28, 2026 at 01:42 UTC f800ddcb12685b2b317665e704ba5acafc4d39f7
88 files changed +2100 -753
src/go/plugin/go.d/collector/clickhouse/collector.go
+30 -1
@@ -39,6 +39,11 @@ func New() *Collector {
39 Timeout: confopt.Duration(time.Second),
40 },
41 },
42 + Functions: FunctionsConfig{
43 + TopQueries: TopQueriesConfig{
44 + Limit: 500,
45 + },
46 + },
47 },
48 charts: chCharts.Copy(),
49 seenDisks: make(map[string]*seenDisk),
@@ -51,7 +56,31 @@ type Config struct {
56 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
57 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
58 web.HTTPConfig `yaml:",inline" json:""`
54 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
59 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
60 +}
61 +
62 +type FunctionsConfig struct {
63 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
64 +}
65 +
66 +type TopQueriesConfig struct {
67 + Disabled bool `yaml:"disabled" json:"disabled"`
68 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
69 + Limit int `yaml:"limit,omitempty" json:"limit"`
70 +}
71 +
72 +func (c Config) topQueriesTimeout() time.Duration {
73 + if c.Functions.TopQueries.Timeout == 0 {
74 + return c.Timeout.Duration()
75 + }
76 + return c.Functions.TopQueries.Timeout.Duration()
77 +}
78 +
79 +func (c Config) topQueriesLimit() int {
80 + if c.Functions.TopQueries.Limit <= 0 {
81 + return 500
82 + }
83 + return c.Functions.TopQueries.Limit
84 }
85
86 type (
src/go/plugin/go.d/collector/clickhouse/config_schema.json
+39 -7
@@ -32,13 +32,39 @@
32 "minimum": 0.5,
33 "default": 1
34 },
35 - "top_queries_limit": {
36 - "title": "Top Queries Limit",
37 - "description": "Maximum number of queries to return in the top-queries function response.",
38 - "type": "integer",
39 - "minimum": 1,
40 - "maximum": 5000,
41 - "default": 500
35 + "functions": {
36 + "title": "Functions",
37 + "description": "Configuration for Netdata functions exposed by this collector.",
38 + "type": "object",
39 + "properties": {
40 + "top_queries": {
41 + "title": "Top Queries",
42 + "description": "Configuration for the top-queries function.",
43 + "type": "object",
44 + "properties": {
45 + "disabled": {
46 + "title": "Disabled",
47 + "description": "Disable the top-queries function.",
48 + "type": "boolean",
49 + "default": false
50 + },
51 + "timeout": {
52 + "title": "Timeout",
53 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
54 + "type": "number",
55 + "minimum": 0
56 + },
57 + "limit": {
58 + "title": "Limit",
59 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
60 + "type": "integer",
61 + "minimum": 0,
62 + "maximum": 5000,
63 + "default": 500
64 + }
65 + }
66 + }
67 + }
68 },
69 "not_follow_redirects": {
70 "title": "Not follow redirects",
@@ -217,6 +243,12 @@
243 "fields": [
244 "headers"
245 ]
246 + },
247 + {
248 + "title": "Functions",
249 + "fields": [
250 + "functions"
251 + ]
252 }
253 ]
254 }
src/go/plugin/go.d/collector/clickhouse/func_top_queries.go
+10 -5
@@ -98,6 +98,9 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
98 }
99 switch method {
100 case topQueriesMethodID:
101 + if f.router.collector.Functions.TopQueries.Disabled {
102 + return nil, fmt.Errorf("top-queries function disabled in configuration")
103 + }
104 return f.methodParams(ctx)
105 default:
106 return nil, fmt.Errorf("unknown method: %s", method)
@@ -111,7 +114,12 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
114 }
115 switch method {
116 case topQueriesMethodID:
114 - return f.collectData(ctx, params.Column("__sort"))
117 + if f.router.collector.Functions.TopQueries.Disabled {
118 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
119 + }
120 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
121 + defer cancel()
122 + return f.collectData(queryCtx, params.Column("__sort"))
123 default:
124 return funcapi.NotFoundResponse(method)
125 }
@@ -177,10 +185,7 @@ func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *fu
185 cs := f.columnSet(cols)
186 sortColumn = f.mapAndValidateSortColumn(sortColumn, cs)
187
180 - limit := f.router.collector.TopQueriesLimit
181 - if limit <= 0 {
182 - limit = 500
183 - }
188 + limit := f.router.collector.topQueriesLimit()
189
190 groupKey := "normalized_query_hash"
191 if !availableCols[groupKey] {
src/go/plugin/go.d/collector/clickhouse/metadata.yaml
+16
@@ -163,6 +163,22 @@ modules:
163 required: false
164 group: Request
165
166 + - name: functions.top_queries.disabled
167 + description: Disable the [top-queries](#top-queries) function.
168 + default_value: false
169 + required: false
170 + group: Functions
171 + - name: functions.top_queries.timeout
172 + description: Query timeout (seconds). Uses collector timeout if not set.
173 + default_value: ""
174 + required: false
175 + group: Functions
176 + - name: functions.top_queries.limit
177 + description: Maximum number of queries to return.
178 + default_value: 500
179 + required: false
180 + group: Functions
181 +
182 - name: vnode
183 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
184 default_value: ""
src/go/plugin/go.d/collector/clickhouse/testdata/config.json
+8 -1
@@ -20,5 +20,12 @@
20 "tls_cert": "ok",
21 "tls_key": "ok",
22 "tls_skip_verify": true,
23 - "force_http2": true
23 + "force_http2": true,
24 + "functions": {
25 + "top_queries": {
26 + "disabled": true,
27 + "timeout": 123.123,
28 + "limit": 123
29 + }
30 + }
31 }
src/go/plugin/go.d/collector/clickhouse/testdata/config.yaml
+5
@@ -19,3 +19,8 @@ tls_cert: "ok"
19 tls_key: "ok"
20 tls_skip_verify: yes
21 force_http2: yes
22 +functions:
23 + top_queries:
24 + disabled: true
25 + timeout: 123.123
26 + limit: 123
src/go/plugin/go.d/collector/cockroachdb/collector.go
+58 -7
@@ -46,22 +46,73 @@ func New() *Collector {
46 Timeout: confopt.Duration(time.Second),
47 },
48 },
49 - SQLTimeout: confopt.Duration(time.Second),
49 + Functions: FunctionsConfig{
50 + TopQueries: TopQueriesConfig{
51 + Limit: 500,
52 + },
53 + RunningQueries: RunningQueriesConfig{
54 + Limit: 500,
55 + },
56 + },
57 },
58 charts: charts.Copy(),
59 }
60 }
61
62 type Config struct {
56 - Vnode string `yaml:"vnode,omitempty" json:"vnode"`
57 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
58 - AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
59 - DSN string `yaml:"dsn,omitempty" json:"dsn,omitempty"`
60 - SQLTimeout confopt.Duration `yaml:"sql_timeout,omitempty" json:"sql_timeout,omitempty"`
61 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
63 + Vnode string `yaml:"vnode,omitempty" json:"vnode"`
64 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
65 + AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
66 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
67 web.HTTPConfig `yaml:",inline" json:""`
68 }
69
70 +type FunctionsConfig struct {
71 + DSN string `yaml:"dsn,omitempty" json:"dsn,omitempty"`
72 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
73 + RunningQueries RunningQueriesConfig `yaml:"running_queries,omitempty" json:"running_queries"`
74 +}
75 +
76 +type TopQueriesConfig struct {
77 + Disabled bool `yaml:"disabled" json:"disabled"`
78 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
79 + Limit int `yaml:"limit,omitempty" json:"limit"`
80 +}
81 +
82 +type RunningQueriesConfig struct {
83 + Disabled bool `yaml:"disabled" json:"disabled"`
84 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
85 + Limit int `yaml:"limit,omitempty" json:"limit"`
86 +}
87 +
88 +func (c Config) topQueriesTimeout() time.Duration {
89 + if c.Functions.TopQueries.Timeout == 0 {
90 + return c.Timeout.Duration()
91 + }
92 + return c.Functions.TopQueries.Timeout.Duration()
93 +}
94 +
95 +func (c Config) topQueriesLimit() int {
96 + if c.Functions.TopQueries.Limit <= 0 {
97 + return 500
98 + }
99 + return c.Functions.TopQueries.Limit
100 +}
101 +
102 +func (c Config) runningQueriesTimeout() time.Duration {
103 + if c.Functions.RunningQueries.Timeout == 0 {
104 + return c.Timeout.Duration()
105 + }
106 + return c.Functions.RunningQueries.Timeout.Duration()
107 +}
108 +
109 +func (c Config) runningQueriesLimit() int {
110 + if c.Functions.RunningQueries.Limit <= 0 {
111 + return 500
112 + }
113 + return c.Functions.RunningQueries.Limit
114 +}
115 +
116 type Collector struct {
117 module.Base
118 Config `yaml:",inline" json:""`
src/go/plugin/go.d/collector/cockroachdb/config_schema.json
+67 -30
@@ -32,25 +32,71 @@
32 "minimum": 0.5,
33 "default": 1
34 },
35 - "dsn": {
36 - "title": "SQL DSN",
37 - "description": "CockroachDB SQL Data Source Name for query functions (top-queries, running-queries).",
38 - "type": "string"
39 - },
40 - "sql_timeout": {
41 - "title": "SQL Timeout",
42 - "description": "Timeout in seconds for SQL query functions.",
43 - "type": "number",
44 - "minimum": 0.5,
45 - "default": 1
46 - },
47 - "top_queries_limit": {
48 - "title": "Top Queries Limit",
49 - "description": "Maximum number of rows returned by the top-queries and running-queries functions.",
50 - "type": "integer",
51 - "minimum": 1,
52 - "maximum": 5000,
53 - "default": 500
35 + "functions": {
36 + "title": "Functions",
37 + "description": "Configuration for Netdata functions exposed by this collector.",
38 + "type": "object",
39 + "properties": {
40 + "dsn": {
41 + "title": "SQL DSN",
42 + "description": "CockroachDB SQL Data Source Name (required for query functions).",
43 + "type": "string"
44 + },
45 + "top_queries": {
46 + "title": "Top Queries",
47 + "description": "Configuration for the top-queries function.",
48 + "type": "object",
49 + "properties": {
50 + "disabled": {
51 + "title": "Disabled",
52 + "description": "Disable the top-queries function.",
53 + "type": "boolean",
54 + "default": false
55 + },
56 + "timeout": {
57 + "title": "Timeout",
58 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
59 + "type": "number",
60 + "minimum": 0
61 + },
62 + "limit": {
63 + "title": "Limit",
64 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
65 + "type": "integer",
66 + "minimum": 0,
67 + "maximum": 5000,
68 + "default": 500
69 + }
70 + }
71 + },
72 + "running_queries": {
73 + "title": "Running Queries",
74 + "description": "Configuration for the running-queries function.",
75 + "type": "object",
76 + "properties": {
77 + "disabled": {
78 + "title": "Disabled",
79 + "description": "Disable the running-queries function.",
80 + "type": "boolean",
81 + "default": false
82 + },
83 + "timeout": {
84 + "title": "Timeout",
85 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
86 + "type": "number",
87 + "minimum": 0
88 + },
89 + "limit": {
90 + "title": "Limit",
91 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
92 + "type": "integer",
93 + "minimum": 0,
94 + "maximum": 5000,
95 + "default": 500
96 + }
97 + }
98 + }
99 + }
100 },
101 "not_follow_redirects": {
102 "title": "Not follow redirects",
@@ -164,11 +210,9 @@
210 ]
211 },
212 {
167 - "title": "SQL",
213 + "title": "Functions",
214 "fields": [
169 - "dsn",
170 - "sql_timeout",
171 - "top_queries_limit"
215 + "functions"
216 ]
217 },
218 {
@@ -228,13 +272,6 @@
272 "timeout": {
273 "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
274 },
231 - "dsn": {
232 - "ui:help": "Format is `postgres://username:password@host:port/dbname?sslmode=disable`.",
233 - "ui:placeholder": "postgres://username:password@host:port/defaultdb?sslmode=disable"
234 - },
235 - "sql_timeout": {
236 - "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
237 - },
275 "username": {
276 "ui:widget": "password"
277 },
src/go/plugin/go.d/collector/cockroachdb/func_router.go
+6 -9
@@ -19,7 +19,7 @@ var errSQLDSNNotSet = errors.New("SQL DSN is not set")
19 // funcRouter routes method calls to appropriate function handlers.
20 // Owns shared SQL connection used by all function handlers.
21 type funcRouter struct {
22 - collector *Collector // for config (DSN, SQLTimeout, TopQueriesLimit, logger)
22 + collector *Collector // for config (Functions.DSN, logger)
23
24 // Shared SQL connection
25 db *sql.DB
@@ -75,11 +75,11 @@ func (r *funcRouter) ensureDB(ctx context.Context) error {
75 if r.db != nil {
76 return nil
77 }
78 - if r.collector.DSN == "" {
78 + if r.collector.Functions.DSN == "" {
79 return errSQLDSNNotSet
80 }
81
82 - db, err := sql.Open("pgx", r.collector.DSN)
82 + db, err := sql.Open("pgx", r.collector.Functions.DSN)
83 if err != nil {
84 return fmt.Errorf("error opening SQL connection: %w", err)
85 }
@@ -106,17 +106,14 @@ func (r *funcRouter) ensureDB(ctx context.Context) error {
106 }
107
108 func (r *funcRouter) sqlTimeout() time.Duration {
109 - if r.collector.SQLTimeout.Duration() > 0 {
110 - return r.collector.SQLTimeout.Duration()
109 + if r.collector.Timeout.Duration() > 0 {
110 + return r.collector.Timeout.Duration()
111 }
112 return time.Second
113 }
114
115 func (r *funcRouter) topQueriesLimit() int {
116 - if r.collector.TopQueriesLimit > 0 {
117 - return r.collector.TopQueriesLimit
118 - }
119 - return 500
116 + return r.collector.topQueriesLimit()
117 }
118
119 func cockroachMethods() []funcapi.MethodConfig {
src/go/plugin/go.d/collector/cockroachdb/func_running_queries.go
+8 -2
@@ -72,12 +72,18 @@ func newFuncRunningQueries(r *funcRouter) *funcRunningQueries {
72 var _ funcapi.MethodHandler = (*funcRunningQueries)(nil)
73
74 func (f *funcRunningQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
75 + if f.router.collector.Functions.RunningQueries.Disabled {
76 + return nil, fmt.Errorf("running-queries function disabled in configuration")
77 + }
78 return []funcapi.ParamConfig{funcapi.BuildSortParam(runningQueriesColumns)}, nil
79 }
80
81 func (f *funcRunningQueries) Cleanup(ctx context.Context) {}
82
83 func (f *funcRunningQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
84 + if f.router.collector.Functions.RunningQueries.Disabled {
85 + return funcapi.UnavailableResponse("running-queries function has been disabled in configuration")
86 + }
87 if err := f.router.ensureDB(ctx); err != nil {
88 status := 503
89 if errors.Is(err, errSQLDSNNotSet) {
@@ -87,10 +93,10 @@ func (f *funcRunningQueries) Handle(ctx context.Context, method string, params f
93 }
94
95 sortColumn := f.resolveSortColumn(params.Column("__sort"))
90 - limit := f.router.topQueriesLimit()
96 + limit := f.router.collector.runningQueriesLimit()
97
98 query := f.buildSQL(sortColumn)
93 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
99 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.runningQueriesTimeout())
100 defer cancel()
101
102 rows, err := f.router.db.QueryContext(queryCtx, query, limit)
src/go/plugin/go.d/collector/cockroachdb/func_top_queries.go
+7 -1
@@ -83,12 +83,18 @@ func newFuncTopQueries(r *funcRouter) *funcTopQueries {
83 var _ funcapi.MethodHandler = (*funcTopQueries)(nil)
84
85 func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
86 + if f.router.collector.Functions.TopQueries.Disabled {
87 + return nil, fmt.Errorf("top-queries function disabled in configuration")
88 + }
89 return []funcapi.ParamConfig{funcapi.BuildSortParam(topQueriesColumns)}, nil
90 }
91
92 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
93
94 func (f *funcTopQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
95 + if f.router.collector.Functions.TopQueries.Disabled {
96 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
97 + }
98 if err := f.router.ensureDB(ctx); err != nil {
99 status := 503
100 if errors.Is(err, errSQLDSNNotSet) {
@@ -101,7 +107,7 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
107 limit := f.router.topQueriesLimit()
108
109 query := f.buildSQL(sortColumn)
104 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
110 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
111 defer cancel()
112
113 rows, err := f.router.db.QueryContext(queryCtx, query, limit)
src/go/plugin/go.d/collector/cockroachdb/metadata.yaml
+40 -16
@@ -78,21 +78,6 @@ modules:
78 default_value: 1
79 required: false
80 group: Target
81 - - name: dsn
82 - description: SQL DSN used by `top-queries` and `running-queries` functions.
83 - default_value: ""
84 - required: false
85 - group: Query Functions
86 - - name: sql_timeout
87 - description: SQL query timeout (seconds) for query functions.
88 - default_value: 1
89 - required: false
90 - group: Query Functions
91 - - name: top_queries_limit
92 - description: Maximum number of rows returned by the `top-queries` and `running-queries` functions.
93 - default_value: 500
94 - required: false
95 - group: Limits
81
82 - name: username
83 description: Username for Basic HTTP authentication.
@@ -173,6 +158,44 @@ modules:
158 required: false
159 group: Request
160
161 + - name: functions.dsn
162 + description: SQL DSN (required for query functions).
163 + default_value: ""
164 + required: false
165 + group: Functions
166 +
167 + - name: functions.top_queries.disabled
168 + description: Disable the [top-queries](#top-queries) function.
169 + default_value: false
170 + required: false
171 + group: Functions
172 + - name: functions.top_queries.timeout
173 + description: Query timeout (seconds). Uses collector timeout if not set.
174 + default_value: ""
175 + required: false
176 + group: Functions
177 + - name: functions.top_queries.limit
178 + description: Maximum number of queries to return.
179 + default_value: 500
180 + required: false
181 + group: Functions
182 +
183 + - name: functions.running_queries.disabled
184 + description: Disable the [running-queries](#running-queries) function.
185 + default_value: false
186 + required: false
187 + group: Functions
188 + - name: functions.running_queries.timeout
189 + description: Query timeout (seconds). Uses collector timeout if not set.
190 + default_value: ""
191 + required: false
192 + group: Functions
193 + - name: functions.running_queries.limit
194 + description: Maximum number of queries to return.
195 + default_value: 500
196 + required: false
197 + group: Functions
198 +
199 - name: vnode
200 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
201 default_value: ""
@@ -195,7 +218,8 @@ modules:
218 jobs:
219 - name: local
220 url: http://127.0.0.1:8080/_status/vars
198 - dsn: postgres://root@127.0.0.1:26257/defaultdb?sslmode=disable
221 + functions:
222 + dsn: postgres://root@127.0.0.1:26257/defaultdb?sslmode=disable
223 - name: HTTP authentication
224 description: Local server with basic HTTP authentication.
225 config: |
src/go/plugin/go.d/collector/cockroachdb/testdata/config.json
+14 -1
@@ -20,5 +20,18 @@
20 "tls_cert": "ok",
21 "tls_key": "ok",
22 "tls_skip_verify": true,
23 - "force_http2": true
23 + "force_http2": true,
24 + "functions": {
25 + "dsn": "ok",
26 + "top_queries": {
27 + "disabled": true,
28 + "timeout": 123.123,
29 + "limit": 123
30 + },
31 + "running_queries": {
32 + "disabled": true,
33 + "timeout": 123.123,
34 + "limit": 123
35 + }
36 + }
37 }
src/go/plugin/go.d/collector/cockroachdb/testdata/config.yaml
+10
@@ -19,3 +19,13 @@ tls_cert: "ok"
19 tls_key: "ok"
20 tls_skip_verify: yes
21 force_http2: yes
22 +functions:
23 + dsn: "ok"
24 + top_queries:
25 + disabled: yes
26 + timeout: 123.123
27 + limit: 123
28 + running_queries:
29 + disabled: yes
30 + timeout: 123.123
31 + limit: 123
src/go/plugin/go.d/collector/couchbase/collector.go
+31 -2
@@ -42,6 +42,11 @@ func New() *Collector {
42 Timeout: confopt.Duration(time.Second),
43 },
44 },
45 + Functions: FunctionsConfig{
46 + TopQueries: TopQueriesConfig{
47 + Limit: 500,
48 + },
49 + },
50 },
51 collectedBuckets: make(map[string]bool),
52 }
@@ -52,8 +57,32 @@ type Config struct {
57 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
58 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
59 web.HTTPConfig `yaml:",inline" json:""`
55 - QueryURL string `yaml:"query_url,omitempty" json:"query_url,omitempty"`
56 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
60 + QueryURL string `yaml:"query_url,omitempty" json:"query_url,omitempty"`
61 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
62 +}
63 +
64 +type FunctionsConfig struct {
65 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
66 +}
67 +
68 +type TopQueriesConfig struct {
69 + Disabled bool `yaml:"disabled" json:"disabled"`
70 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
71 + Limit int `yaml:"limit,omitempty" json:"limit"`
72 +}
73 +
74 +func (c Config) topQueriesTimeout() time.Duration {
75 + if c.Functions.TopQueries.Timeout == 0 {
76 + return c.Timeout.Duration()
77 + }
78 + return c.Functions.TopQueries.Timeout.Duration()
79 +}
80 +
81 +func (c Config) topQueriesLimit() int {
82 + if c.Functions.TopQueries.Limit <= 0 {
83 + return 500
84 + }
85 + return c.Functions.TopQueries.Limit
86 }
87
88 type Collector struct {
src/go/plugin/go.d/collector/couchbase/config_schema.json
+39 -8
@@ -38,13 +38,39 @@
38 "minimum": 0.5,
39 "default": 1
40 },
41 - "top_queries_limit": {
42 - "title": "Top Queries Limit",
43 - "description": "Maximum number of queries to return in the top-queries function response.",
44 - "type": "integer",
45 - "minimum": 1,
46 - "maximum": 5000,
47 - "default": 500
41 + "functions": {
42 + "title": "Functions",
43 + "description": "Configuration for Netdata functions exposed by this collector.",
44 + "type": "object",
45 + "properties": {
46 + "top_queries": {
47 + "title": "Top Queries",
48 + "description": "Configuration for the top-queries function.",
49 + "type": "object",
50 + "properties": {
51 + "disabled": {
52 + "title": "Disabled",
53 + "description": "Disable the top-queries function.",
54 + "type": "boolean",
55 + "default": false
56 + },
57 + "timeout": {
58 + "title": "Timeout",
59 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
60 + "type": "number",
61 + "minimum": 0
62 + },
63 + "limit": {
64 + "title": "Limit",
65 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
66 + "type": "integer",
67 + "minimum": 0,
68 + "maximum": 5000,
69 + "default": 500
70 + }
71 + }
72 + }
73 + }
74 },
75 "not_follow_redirects": {
76 "title": "Not follow redirects",
@@ -155,7 +181,6 @@
181 "query_url",
182 "timeout",
183 "not_follow_redirects",
158 - "top_queries_limit",
184 "vnode"
185 ]
186 },
@@ -188,6 +213,12 @@
213 "fields": [
214 "headers"
215 ]
216 + },
217 + {
218 + "title": "Functions",
219 + "fields": [
220 + "functions"
221 + ]
222 }
223 ]
224 },
src/go/plugin/go.d/collector/couchbase/func_top_queries.go
+10 -5
@@ -118,6 +118,9 @@ var _ funcapi.MethodHandler = (*funcTopQueries)(nil)
118 func (f *funcTopQueries) MethodParams(_ context.Context, method string) ([]funcapi.ParamConfig, error) {
119 switch method {
120 case topQueriesMethodID:
121 + if f.router.collector.Functions.TopQueries.Disabled {
122 + return nil, fmt.Errorf("top-queries function disabled in configuration")
123 + }
124 return []funcapi.ParamConfig{funcapi.BuildSortParam(topQueriesColumns)}, nil
125 default:
126 return nil, fmt.Errorf("unknown method: %s", method)
@@ -132,7 +135,12 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
135
136 switch method {
137 case topQueriesMethodID:
135 - return f.collectData(ctx, params.Column("__sort"))
138 + if f.router.collector.Functions.TopQueries.Disabled {
139 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
140 + }
141 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
142 + defer cancel()
143 + return f.collectData(queryCtx, params.Column("__sort"))
144 default:
145 return funcapi.NotFoundResponse(method)
146 }
@@ -142,10 +150,7 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
150 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
151
152 func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
145 - limit := f.router.collector.TopQueriesLimit
146 - if limit <= 0 {
147 - limit = 500
148 - }
153 + limit := f.router.collector.topQueriesLimit()
154
155 statement := "SELECT cr.requestId, cr.requestTime, cr.statement, cr.elapsedTime, cr.serviceTime, " +
156 "cr.resultCount, cr.resultSize, cr.errorCount, cr.warningCount, cr.users AS `user`, cr.clientContextID " +
src/go/plugin/go.d/collector/couchbase/metadata.yaml
+16
@@ -151,6 +151,22 @@ modules:
151 required: false
152 group: Request
153
154 + - name: functions.top_queries.disabled
155 + description: Disable the [top-queries](#top-queries) function.
156 + default_value: false
157 + required: false
158 + group: Functions
159 + - name: functions.top_queries.timeout
160 + description: Query timeout (seconds). Uses collector timeout if not set.
161 + default_value: ""
162 + required: false
163 + group: Functions
164 + - name: functions.top_queries.limit
165 + description: Maximum number of queries to return.
166 + default_value: 500
167 + required: false
168 + group: Functions
169 +
170 - name: vnode
171 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
172 default_value: ""
src/go/plugin/go.d/collector/couchbase/testdata/config.json
+8 -1
@@ -20,5 +20,12 @@
20 "tls_cert": "ok",
21 "tls_key": "ok",
22 "tls_skip_verify": true,
23 - "force_http2": true
23 + "force_http2": true,
24 + "functions": {
25 + "top_queries": {
26 + "disabled": true,
27 + "timeout": 123.123,
28 + "limit": 123
29 + }
30 + }
31 }
src/go/plugin/go.d/collector/couchbase/testdata/config.yaml
+5
@@ -19,3 +19,8 @@ tls_cert: "ok"
19 tls_key: "ok"
20 tls_skip_verify: yes
21 force_http2: yes
22 +functions:
23 + top_queries:
24 + disabled: true
25 + timeout: 123.123
26 + limit: 123
src/go/plugin/go.d/collector/elasticsearch/collector.go
+35 -6
@@ -49,6 +49,11 @@ func New() *Collector {
49 DoClusterStats: true,
50 DoClusterHealth: true,
51 DoIndicesStats: false,
52 + Functions: FunctionsConfig{
53 + TopQueries: TopQueriesConfig{
54 + Limit: 500,
55 + },
56 + },
57 },
58
59 charts: &module.Charts{},
@@ -64,12 +69,36 @@ type Config struct {
69 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
70 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
71 web.HTTPConfig `yaml:",inline" json:""`
67 - ClusterMode bool `yaml:"cluster_mode" json:"cluster_mode"`
68 - DoNodeStats bool `yaml:"collect_node_stats" json:"collect_node_stats"`
69 - DoClusterHealth bool `yaml:"collect_cluster_health" json:"collect_cluster_health"`
70 - DoClusterStats bool `yaml:"collect_cluster_stats" json:"collect_cluster_stats"`
71 - DoIndicesStats bool `yaml:"collect_indices_stats" json:"collect_indices_stats"`
72 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
72 + ClusterMode bool `yaml:"cluster_mode" json:"cluster_mode"`
73 + DoNodeStats bool `yaml:"collect_node_stats" json:"collect_node_stats"`
74 + DoClusterHealth bool `yaml:"collect_cluster_health" json:"collect_cluster_health"`
75 + DoClusterStats bool `yaml:"collect_cluster_stats" json:"collect_cluster_stats"`
76 + DoIndicesStats bool `yaml:"collect_indices_stats" json:"collect_indices_stats"`
77 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
78 +}
79 +
80 +type FunctionsConfig struct {
81 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
82 +}
83 +
84 +type TopQueriesConfig struct {
85 + Disabled bool `yaml:"disabled" json:"disabled"`
86 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
87 + Limit int `yaml:"limit,omitempty" json:"limit"`
88 +}
89 +
90 +func (c Config) topQueriesTimeout() time.Duration {
91 + if c.Functions.TopQueries.Timeout == 0 {
92 + return c.Timeout.Duration()
93 + }
94 + return c.Functions.TopQueries.Timeout.Duration()
95 +}
96 +
97 +func (c Config) topQueriesLimit() int {
98 + if c.Functions.TopQueries.Limit <= 0 {
99 + return 500
100 + }
101 + return c.Functions.TopQueries.Limit
102 }
103
104 type Collector struct {
src/go/plugin/go.d/collector/elasticsearch/config_schema.json
+39 -7
@@ -32,13 +32,39 @@
32 "minimum": 0.5,
33 "default": 2
34 },
35 - "top_queries_limit": {
36 - "title": "Top Queries Limit",
37 - "description": "Maximum number of queries to return in the top-queries function response.",
38 - "type": "integer",
39 - "minimum": 1,
40 - "maximum": 5000,
41 - "default": 500
35 + "functions": {
36 + "title": "Functions",
37 + "description": "Configuration for Netdata functions exposed by this collector.",
38 + "type": "object",
39 + "properties": {
40 + "top_queries": {
41 + "title": "Top Queries",
42 + "description": "Configuration for the top-queries function.",
43 + "type": "object",
44 + "properties": {
45 + "disabled": {
46 + "title": "Disabled",
47 + "description": "Disable the top-queries function.",
48 + "type": "boolean",
49 + "default": false
50 + },
51 + "timeout": {
52 + "title": "Timeout",
53 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
54 + "type": "number",
55 + "minimum": 0
56 + },
57 + "limit": {
58 + "title": "Limit",
59 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
60 + "type": "integer",
61 + "minimum": 0,
62 + "maximum": 5000,
63 + "default": 500
64 + }
65 + }
66 + }
67 + }
68 },
69 "not_follow_redirects": {
70 "title": "Not follow redirects",
@@ -215,6 +241,12 @@
241 "fields": [
242 "headers"
243 ]
244 + },
245 + {
246 + "title": "Functions",
247 + "fields": [
248 + "functions"
249 + ]
250 }
251 ]
252 },
src/go/plugin/go.d/collector/elasticsearch/func_top_queries.go
+10 -5
@@ -108,6 +108,9 @@ func (f *funcTopQueries) Cleanup(ctx context.Context) {}
108 func (f *funcTopQueries) MethodParams(_ context.Context, method string) ([]funcapi.ParamConfig, error) {
109 switch method {
110 case topQueriesMethodID:
111 + if f.router.collector.Functions.TopQueries.Disabled {
112 + return nil, fmt.Errorf("top-queries function disabled in configuration")
113 + }
114 return []funcapi.ParamConfig{funcapi.BuildSortParam(topQueriesColumns)}, nil
115 default:
116 return nil, fmt.Errorf("unknown method: %s", method)
@@ -122,17 +125,19 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
125
126 switch method {
127 case topQueriesMethodID:
125 - return f.collectData(ctx, params.Column("__sort"))
128 + if f.router.collector.Functions.TopQueries.Disabled {
129 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
130 + }
131 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
132 + defer cancel()
133 + return f.collectData(queryCtx, params.Column("__sort"))
134 default:
135 return funcapi.NotFoundResponse(method)
136 }
137 }
138
139 func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
132 - limit := f.router.collector.TopQueriesLimit
133 - if limit <= 0 {
134 - limit = 500
135 - }
140 + limit := f.router.collector.topQueriesLimit()
141
142 req, err := web.NewHTTPRequestWithPath(f.router.collector.RequestConfig, "/_tasks")
143 if err != nil {
src/go/plugin/go.d/collector/elasticsearch/metadata.yaml
+16
@@ -200,6 +200,22 @@ modules:
200 required: false
201 group: Request
202
203 + - name: functions.top_queries.disabled
204 + description: Disable the [top-queries](#top-queries) function.
205 + default_value: false
206 + required: false
207 + group: Functions
208 + - name: functions.top_queries.timeout
209 + description: Query timeout (seconds). Uses collector timeout if not set.
210 + default_value: ""
211 + required: false
212 + group: Functions
213 + - name: functions.top_queries.limit
214 + description: Maximum number of queries to return.
215 + default_value: 500
216 + required: false
217 + group: Functions
218 +
219 - name: vnode
220 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
221 default_value: ""
src/go/plugin/go.d/collector/elasticsearch/testdata/config.json
+8 -1
@@ -25,5 +25,12 @@
25 "collect_node_stats": true,
26 "collect_cluster_health": true,
27 "collect_cluster_stats": true,
28 - "collect_indices_stats": true
28 + "collect_indices_stats": true,
29 + "functions": {
30 + "top_queries": {
31 + "disabled": true,
32 + "timeout": 123.123,
33 + "limit": 123
34 + }
35 + }
36 }
src/go/plugin/go.d/collector/elasticsearch/testdata/config.yaml
+5
@@ -24,3 +24,8 @@ collect_node_stats: yes
24 collect_cluster_health: yes
25 collect_cluster_stats: yes
26 collect_indices_stats: yes
27 +functions:
28 + top_queries:
29 + disabled: true
30 + timeout: 123.123
31 + limit: 123
src/go/plugin/go.d/collector/mongodb/collector.go
+33 -14
@@ -37,6 +37,11 @@ func New() *Collector {
37 Includes: []string{},
38 Excludes: []string{},
39 },
40 + Functions: FunctionsConfig{
41 + TopQueries: TopQueriesConfig{
42 + Limit: 500,
43 + },
44 + },
45 },
46
47 conn: &mongoClient{},
@@ -52,23 +57,37 @@ func New() *Collector {
57 }
58
59 type Config struct {
55 - Vnode string `yaml:"vnode,omitempty" json:"vnode"`
56 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
57 - AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
58 - URI string `yaml:"uri" json:"uri"`
59 - Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
60 - Databases matcher.SimpleExpr `yaml:"databases,omitempty" json:"databases"`
61 - TopQueriesFunctionEnabled *bool `yaml:"top_queries_function_enabled,omitempty" json:"top_queries_function_enabled,omitempty"`
62 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
60 + Vnode string `yaml:"vnode,omitempty" json:"vnode"`
61 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
62 + AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
63 + URI string `yaml:"uri" json:"uri"`
64 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
65 + Databases matcher.SimpleExpr `yaml:"databases,omitempty" json:"databases"`
66 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
67 +}
68 +
69 +type FunctionsConfig struct {
70 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
71 +}
72 +
73 +type TopQueriesConfig struct {
74 + Disabled bool `yaml:"disabled" json:"disabled"`
75 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
76 + Limit int `yaml:"limit,omitempty" json:"limit"`
77 +}
78 +
79 +func (c Config) topQueriesTimeout() time.Duration {
80 + if c.Functions.TopQueries.Timeout == 0 {
81 + return c.Timeout.Duration()
82 + }
83 + return c.Functions.TopQueries.Timeout.Duration()
84 }
85
65 -// GetTopQueriesFunctionEnabled returns whether the top queries function is enabled.
66 -// Defaults to true if not explicitly configured.
67 -func (c *Config) GetTopQueriesFunctionEnabled() bool {
68 - if c.TopQueriesFunctionEnabled == nil {
69 - return true
86 +func (c Config) topQueriesLimit() int {
87 + if c.Functions.TopQueries.Limit <= 0 {
88 + return 500
89 }
71 - return *c.TopQueriesFunctionEnabled
90 + return c.Functions.TopQueries.Limit
91 }
92
93 type Collector struct {
src/go/plugin/go.d/collector/mongodb/config_schema.json
+41 -21
@@ -67,19 +67,39 @@
67 }
68 }
69 },
70 - "top_queries_function_enabled": {
71 - "title": "Enable Top Queries function",
72 - "description": "Enables or disables the Top Queries function that exposes slow queries from MongoDB Profiler (system.profile). **WARNING**: Query text may contain unmasked literals (potential PII). Requires profiling to be enabled on target databases.",
73 - "type": "boolean",
74 - "default": true
75 - },
76 - "top_queries_limit": {
77 - "title": "Top Queries limit",
78 - "description": "Maximum number of queries to return from the Top Queries function. Set to 0 to use the default limit (500).",
79 - "type": "integer",
80 - "minimum": 0,
81 - "maximum": 10000,
82 - "default": 500
70 + "functions": {
71 + "title": "Functions",
72 + "description": "Configuration for Netdata functions exposed by this collector.",
73 + "type": "object",
74 + "properties": {
75 + "top_queries": {
76 + "title": "Top Queries",
77 + "description": "Configuration for the top-queries function (MongoDB Profiler).",
78 + "type": "object",
79 + "properties": {
80 + "disabled": {
81 + "title": "Disabled",
82 + "description": "Disable the top-queries function.",
83 + "type": "boolean",
84 + "default": false
85 + },
86 + "timeout": {
87 + "title": "Timeout",
88 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
89 + "type": "number",
90 + "minimum": 0
91 + },
92 + "limit": {
93 + "title": "Limit",
94 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
95 + "type": "integer",
96 + "minimum": 0,
97 + "maximum": 5000,
98 + "default": 500
99 + }
100 + }
101 + }
102 + }
103 },
104 "vnode": {
105 "title": "Vnode",
@@ -110,11 +130,12 @@
130 "databases": {
131 "ui:help": "The logic for inclusion and exclusion is as follows: `(include1 OR include2) AND !(exclude1 OR exclude2)`."
132 },
113 - "top_queries_function_enabled": {
114 - "ui:help": "When enabled, the Top Queries function allows you to view slow queries from MongoDB's system.profile collection. Note: Query text may contain unmasked literals which could include sensitive information (PII)."
115 - },
116 - "top_queries_limit": {
117 - "ui:help": "Controls how many queries are returned. Higher values provide more data but may impact performance."
133 + "functions": {
134 + "top_queries": {
135 + "disabled": {
136 + "ui:help": "WARNING: Query text from MongoDB Profiler may contain unmasked literals (PII). Requires profiling to be enabled on target databases."
137 + }
138 + }
139 },
140 "ui:flavour": "tabs",
141 "ui:options": {
@@ -136,10 +157,9 @@
157 ]
158 },
159 {
139 - "title": "Top Queries",
160 + "title": "Functions",
161 "fields": [
141 - "top_queries_function_enabled",
142 - "top_queries_limit"
162 + "functions"
163 ]
164 }
165 ]
src/go/plugin/go.d/collector/mongodb/func_top_queries.go
+9 -10
@@ -160,6 +160,9 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
160 }
161 switch method {
162 case topQueriesMethodID:
163 + if f.router.collector.Functions.TopQueries.Disabled {
164 + return nil, fmt.Errorf("top-queries function disabled in configuration")
165 + }
166 return f.methodParams(ctx)
167 default:
168 return nil, fmt.Errorf("unknown method: %s", method)
@@ -174,19 +177,18 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
177
178 switch method {
179 case topQueriesMethodID:
177 - if !f.router.collector.Config.GetTopQueriesFunctionEnabled() {
178 - return funcapi.ErrorResponse(403, "Top Queries function has been disabled in configuration. Set 'top_queries_function_enabled: true' to enable.")
180 + if f.router.collector.Functions.TopQueries.Disabled {
181 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
182 }
180 - return f.collectData(ctx, params.Column(topQueriesParamSort))
183 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
184 + defer cancel()
185 + return f.collectData(queryCtx, params.Column(topQueriesParamSort))
186 default:
187 return funcapi.NotFoundResponse(method)
188 }
189 }
190
191 func (f *funcTopQueries) methodParams(ctx context.Context) ([]funcapi.ParamConfig, error) {
187 - if !f.router.collector.Config.GetTopQueriesFunctionEnabled() {
188 - return nil, fmt.Errorf("top queries function disabled")
189 - }
192
193 databases, err := f.getDatabases()
194 if err != nil {
@@ -204,10 +206,7 @@ func (f *funcTopQueries) methodParams(ctx context.Context) ([]funcapi.ParamConfi
206 }
207
208 func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
207 - limit := f.router.collector.Config.TopQueriesLimit
208 - if limit <= 0 {
209 - limit = topQueriesDefaultLimit
210 - }
209 + limit := f.router.collector.topQueriesLimit()
210
211 // Build valid sort columns map from metadata
212 validSortCols := make(map[string]bool)
src/go/plugin/go.d/collector/mongodb/metadata.yaml
+16
@@ -124,6 +124,22 @@ modules:
124 - pattern4
125 ```
126
127 + - name: functions.top_queries.disabled
128 + description: Disable the [top-queries](#top-queries) function.
129 + default_value: false
130 + required: false
131 + group: Functions
132 + - name: functions.top_queries.timeout
133 + description: Query timeout (seconds). Uses collector timeout if not set.
134 + default_value: ""
135 + required: false
136 + group: Functions
137 + - name: functions.top_queries.limit
138 + description: Maximum number of queries to return.
139 + default_value: 500
140 + required: false
141 + group: Functions
142 +
143 - name: vnode
144 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
145 default_value: ""
src/go/plugin/go.d/collector/mongodb/testdata/config.json
+7
@@ -11,5 +11,12 @@
11 "excludes": [
12 "ok"
13 ]
14 + },
15 + "functions": {
16 + "top_queries": {
17 + "disabled": true,
18 + "timeout": 123.123,
19 + "limit": 123
20 + }
21 }
22 }
src/go/plugin/go.d/collector/mongodb/testdata/config.yaml
+5
@@ -8,3 +8,8 @@ databases:
8 - "ok"
9 excludes:
10 - "ok"
11 +functions:
12 + top_queries:
13 + disabled: true
14 + timeout: 123.123
15 + limit: 123
src/go/plugin/go.d/collector/mssql/collector.go
+59 -91
@@ -38,6 +38,12 @@ func New() *Collector {
38 Config: Config{
39 DSN: "sqlserver://localhost:1433",
40 Timeout: confopt.Duration(time.Second * 5),
41 + Functions: FunctionsConfig{
42 + TopQueries: TopQueriesConfig{
43 + Limit: 500,
44 + TimeWindowDays: 7,
45 + },
46 + },
47 },
48
49 charts: instanceCharts.Copy(),
@@ -56,116 +62,78 @@ type Config struct {
62 UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
63 DSN string `yaml:"dsn" json:"dsn"`
64 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
65 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
66 +}
67
60 - // QueryStoreTimeWindowDays controls how far back to look in Query Store
61 - // Uses pointer to distinguish "unset" from explicit "0":
62 - // - nil (unset): Apply default of 7 days
63 - // - 0: Query ALL available data (not recommended for busy servers)
64 - // - N > 0: Query last N days
65 - QueryStoreTimeWindowDays *int `yaml:"query_store_time_window_days,omitempty" json:"query_store_time_window_days"`
66 -
67 - // QueryStoreFunctionEnabled controls whether the top-queries function is available
68 - // Uses pointer to distinguish "unset" from explicit "false":
69 - // - nil (unset): Apply default of true (enabled)
70 - // - false: Explicitly disabled
71 - // - true: Explicitly enabled
72 - // Default: true - MSSQL Query Store may contain unmasked PII in query text
73 - QueryStoreFunctionEnabled *bool `yaml:"query_store_function_enabled,omitempty" json:"query_store_function_enabled"`
74 -
75 - // DeadlockInfoFunctionEnabled controls whether the deadlock-info function is available
76 - // Uses pointer to distinguish "unset" from explicit "false":
77 - // - nil (unset): Apply default of true (enabled)
78 - // - false: Explicitly disabled
79 - // - true: Explicitly enabled
80 - // Default: true
81 - DeadlockInfoFunctionEnabled *bool `yaml:"deadlock_info_function_enabled,omitempty" json:"deadlock_info_function_enabled"`
82 -
83 - // DeadlockInfoUseRingBuffer uses ring_buffer target instead of event_file for deadlock-info
84 - // Uses pointer to distinguish "unset" from explicit "true":
85 - // - nil (unset): Apply default of false (use event_file)
86 - // - false: Use event_file target (faster, recommended for on-prem)
87 - // - true: Use ring_buffer target (required for Azure SQL DB without blob storage)
88 - // Default: false
89 - DeadlockInfoUseRingBuffer *bool `yaml:"deadlock_info_use_ring_buffer,omitempty" json:"deadlock_info_use_ring_buffer"`
90 -
91 - // ErrorInfoFunctionEnabled controls whether the error-info function is available
92 - // Uses pointer to distinguish "unset" from explicit "false":
93 - // - nil (unset): Apply default of true (enabled)
94 - // - false: Explicitly disabled
95 - // - true: Explicitly enabled
96 - // Default: true
97 - ErrorInfoFunctionEnabled *bool `yaml:"error_info_function_enabled,omitempty" json:"error_info_function_enabled"`
98 -
99 - // ErrorInfoSessionName sets the Extended Events session name for error-info
100 - // Default: "netdata_errors"
101 - ErrorInfoSessionName string `yaml:"error_info_session_name,omitempty" json:"error_info_session_name,omitempty"`
102 -
103 - // ErrorInfoUseRingBuffer uses ring_buffer target instead of event_file for error-info
104 - // Uses pointer to distinguish "unset" from explicit "true":
105 - // - nil (unset): Apply default of false (use event_file)
106 - // - false: Use event_file target (faster, recommended for on-prem)
107 - // - true: Use ring_buffer target (required for Azure SQL DB without blob storage)
108 - // Default: false
109 - ErrorInfoUseRingBuffer *bool `yaml:"error_info_use_ring_buffer,omitempty" json:"error_info_use_ring_buffer"`
110 -
111 - // TopQueriesLimit is the maximum number of queries to return
112 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
113 -}
114 -
115 -// GetQueryStoreTimeWindowDays returns the time window for Query Store queries (default: 7)
116 -func (c *Config) GetQueryStoreTimeWindowDays() int {
117 - if c.QueryStoreTimeWindowDays == nil {
118 - return 7
119 - }
120 - return *c.QueryStoreTimeWindowDays
68 +type FunctionsConfig struct {
69 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
70 + DeadlockInfo DeadlockInfoConfig `yaml:"deadlock_info,omitempty" json:"deadlock_info"`
71 + ErrorInfo ErrorInfoConfig `yaml:"error_info,omitempty" json:"error_info"`
72 +}
73 +
74 +type TopQueriesConfig struct {
75 + Disabled bool `yaml:"disabled" json:"disabled"`
76 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
77 + Limit int `yaml:"limit,omitempty" json:"limit"`
78 + TimeWindowDays int `yaml:"time_window_days,omitempty" json:"time_window_days"`
79 +}
80 +
81 +type DeadlockInfoConfig struct {
82 + Disabled bool `yaml:"disabled" json:"disabled"`
83 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
84 + UseRingBuffer bool `yaml:"use_ring_buffer" json:"use_ring_buffer"`
85 }
86
123 -// GetQueryStoreFunctionEnabled returns whether the Query Store function is enabled (default: true)
124 -func (c *Config) GetQueryStoreFunctionEnabled() bool {
125 - if c.QueryStoreFunctionEnabled == nil {
126 - return true
87 +type ErrorInfoConfig struct {
88 + Disabled bool `yaml:"disabled" json:"disabled"`
89 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
90 + SessionName string `yaml:"session_name,omitempty" json:"session_name,omitempty"`
91 + UseRingBuffer bool `yaml:"use_ring_buffer" json:"use_ring_buffer"`
92 +}
93 +
94 +func (c Config) topQueriesTimeout() time.Duration {
95 + if c.Functions.TopQueries.Timeout == 0 {
96 + return c.Timeout.Duration()
97 }
128 - return *c.QueryStoreFunctionEnabled
98 + return c.Functions.TopQueries.Timeout.Duration()
99 }
100
131 -// GetDeadlockInfoFunctionEnabled returns whether the deadlock-info function is enabled (default: true)
132 -func (c *Config) GetDeadlockInfoFunctionEnabled() bool {
133 - if c.DeadlockInfoFunctionEnabled == nil {
134 - return true
101 +func (c Config) topQueriesLimit() int {
102 + if c.Functions.TopQueries.Limit <= 0 {
103 + return 500
104 }
136 - return *c.DeadlockInfoFunctionEnabled
105 + return c.Functions.TopQueries.Limit
106 }
107
139 -// GetDeadlockInfoUseRingBuffer returns whether to use ring_buffer target instead of event_file (default: false)
140 -func (c *Config) GetDeadlockInfoUseRingBuffer() bool {
141 - if c.DeadlockInfoUseRingBuffer == nil {
142 - return false
108 +func (c Config) topQueriesTimeWindowDays() int {
109 + if c.Functions.TopQueries.TimeWindowDays == -1 {
110 + return 0 // -1 means "query all history"
111 + }
112 + if c.Functions.TopQueries.TimeWindowDays <= 0 {
113 + return 7
114 }
144 - return *c.DeadlockInfoUseRingBuffer
115 + return c.Functions.TopQueries.TimeWindowDays
116 }
117
147 -// GetErrorInfoFunctionEnabled returns whether the error-info function is enabled (default: true)
148 -func (c *Config) GetErrorInfoFunctionEnabled() bool {
149 - if c.ErrorInfoFunctionEnabled == nil {
150 - return true
118 +func (c Config) deadlockInfoTimeout() time.Duration {
119 + if c.Functions.DeadlockInfo.Timeout == 0 {
120 + return c.Timeout.Duration()
121 }
152 - return *c.ErrorInfoFunctionEnabled
122 + return c.Functions.DeadlockInfo.Timeout.Duration()
123 }
124
155 -// GetErrorInfoSessionName returns the Extended Events session name for error-info.
156 -func (c *Config) GetErrorInfoSessionName() string {
157 - if strings.TrimSpace(c.ErrorInfoSessionName) == "" {
158 - return "netdata_errors"
125 +func (c Config) errorInfoTimeout() time.Duration {
126 + if c.Functions.ErrorInfo.Timeout == 0 {
127 + return c.Timeout.Duration()
128 }
160 - return c.ErrorInfoSessionName
129 + return c.Functions.ErrorInfo.Timeout.Duration()
130 }
131
163 -// GetErrorInfoUseRingBuffer returns whether to use ring_buffer target instead of event_file (default: false)
164 -func (c *Config) GetErrorInfoUseRingBuffer() bool {
165 - if c.ErrorInfoUseRingBuffer == nil {
166 - return false
132 +func (c Config) errorInfoSessionName() string {
133 + if strings.TrimSpace(c.Functions.ErrorInfo.SessionName) == "" {
134 + return "netdata_errors"
135 }
168 - return *c.ErrorInfoUseRingBuffer
136 + return c.Functions.ErrorInfo.SessionName
137 }
138
139 type Collector struct {
src/go/plugin/go.d/collector/mssql/config_schema.json
+127 -75
@@ -30,56 +30,102 @@
30 "minimum": 0.5,
31 "default": 5
32 },
33 - "query_store_function_enabled": {
34 - "title": "Enable Query Store Function",
35 - "description": "Enable the top-queries function using SQL Server Query Store. WARNING: Query Store may contain unmasked PII (customer names, emails, IDs) in query text. Only enable after ensuring proper access controls to the Netdata dashboard.",
36 - "type": "boolean",
37 - "default": true
38 - },
39 - "query_store_time_window_days": {
40 - "title": "Query Store Time Window (Days)",
41 - "description": "How many days of Query Store data to query for the top-queries function. Set to 0 to query all available data (not recommended for busy servers).",
42 - "type": "integer",
43 - "minimum": 0,
44 - "default": 7
45 - },
46 - "top_queries_limit": {
47 - "title": "Top Queries Limit",
48 - "description": "Maximum number of queries to return in the top-queries function response.",
49 - "type": "integer",
50 - "minimum": 1,
51 - "maximum": 5000,
52 - "default": 500
53 - },
54 - "deadlock_info_function_enabled": {
55 - "title": "Enable Deadlock Info Function",
56 - "description": "Enable the deadlock-info function. WARNING: query text may contain unmasked sensitive literals (PII). This function reads deadlock graphs from the system_health session and requires VIEW SERVER STATE. Grant with: GRANT VIEW SERVER STATE TO [netdata_user];",
57 - "type": "boolean",
58 - "default": true
59 - },
60 - "deadlock_info_use_ring_buffer": {
61 - "title": "Use Ring Buffer for Deadlock Info",
62 - "description": "Use ring_buffer target instead of event_file for system_health session. Enable for Azure SQL Database without blob storage. Note: ring_buffer can be slower with large buffers.",
63 - "type": "boolean",
64 - "default": false
65 - },
66 - "error_info_function_enabled": {
67 - "title": "Enable Error Info Function",
68 - "description": "Enable the error-info function. WARNING: error messages and query text may contain unmasked sensitive literals (PII). This function reads from a user-managed Extended Events session and requires VIEW SERVER STATE.",
69 - "type": "boolean",
70 - "default": true
71 - },
72 - "error_info_session_name": {
73 - "title": "Error Info Session Name",
74 - "description": "Name of the Extended Events session that captures error_reported events for error-info. The session must be created by an administrator and include an event_file target.",
75 - "type": "string",
76 - "default": "netdata_errors"
77 - },
78 - "error_info_use_ring_buffer": {
79 - "title": "Use Ring Buffer for Error Info",
80 - "description": "Use ring_buffer target instead of event_file. Enable for Azure SQL Database without blob storage. Note: ring_buffer can be slower with large buffers.",
81 - "type": "boolean",
82 - "default": false
33 + "functions": {
34 + "title": "Functions",
35 + "description": "Configuration for Netdata functions exposed by this collector.",
36 + "type": "object",
37 + "properties": {
38 + "top_queries": {
39 + "title": "Top Queries",
40 + "description": "Configuration for the top-queries function using SQL Server Query Store.",
41 + "type": "object",
42 + "properties": {
43 + "disabled": {
44 + "title": "Disabled",
45 + "description": "Disable the top-queries function.",
46 + "type": "boolean",
47 + "default": false
48 + },
49 + "timeout": {
50 + "title": "Timeout",
51 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
52 + "type": "number",
53 + "minimum": 0
54 + },
55 + "limit": {
56 + "title": "Limit",
57 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
58 + "type": "integer",
59 + "minimum": 0,
60 + "maximum": 5000,
61 + "default": 500
62 + },
63 + "time_window_days": {
64 + "title": "Time Window (Days)",
65 + "description": "How many days of Query Store data to query. Set to 0 to use the default (7). Set to -1 to query all available data (not recommended for busy servers).",
66 + "type": "integer",
67 + "minimum": -1,
68 + "default": 7
69 + }
70 + }
71 + },
72 + "deadlock_info": {
73 + "title": "Deadlock Info",
74 + "description": "Configuration for the deadlock-info function.",
75 + "type": "object",
76 + "properties": {
77 + "disabled": {
78 + "title": "Disabled",
79 + "description": "Disable the deadlock-info function.",
80 + "type": "boolean",
81 + "default": false
82 + },
83 + "timeout": {
84 + "title": "Timeout",
85 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
86 + "type": "number",
87 + "minimum": 0
88 + },
89 + "use_ring_buffer": {
90 + "title": "Use Ring Buffer",
91 + "description": "Use ring_buffer target instead of event_file for system_health session. Enable for Azure SQL Database without blob storage.",
92 + "type": "boolean",
93 + "default": false
94 + }
95 + }
96 + },
97 + "error_info": {
98 + "title": "Error Info",
99 + "description": "Configuration for the error-info function.",
100 + "type": "object",
101 + "properties": {
102 + "disabled": {
103 + "title": "Disabled",
104 + "description": "Disable the error-info function.",
105 + "type": "boolean",
106 + "default": false
107 + },
108 + "timeout": {
109 + "title": "Timeout",
110 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
111 + "type": "number",
112 + "minimum": 0
113 + },
114 + "session_name": {
115 + "title": "Session Name",
116 + "description": "Name of the Extended Events session that captures error_reported events.",
117 + "type": "string",
118 + "default": "netdata_errors"
119 + },
120 + "use_ring_buffer": {
121 + "title": "Use Ring Buffer",
122 + "description": "Use ring_buffer target instead of event_file. Enable for Azure SQL Database without blob storage.",
123 + "type": "boolean",
124 + "default": false
125 + }
126 + }
127 + }
128 + }
129 }
130 },
131 "required": [
@@ -103,26 +149,34 @@
149 "timeout": {
150 "ui:help": "Accepts decimals for sub-second granularity (e.g., 0.5 for 500ms)."
151 },
106 - "query_store_function_enabled": {
107 - "ui:help": "When enabled, the 'top-queries' function becomes available in the Netdata dashboard. Review the PII warning before enabling."
108 - },
109 - "query_store_time_window_days": {
110 - "ui:help": "Limits Query Store data to recent days. Lower values improve performance on busy servers."
111 - },
112 - "deadlock_info_function_enabled": {
113 - "ui:help": "When enabled, the deadlock-info function becomes available in the Netdata dashboard. WARNING: query text may contain unmasked sensitive literals and requires VIEW SERVER STATE permission."
114 - },
115 - "deadlock_info_use_ring_buffer": {
116 - "ui:help": "Enable for Azure SQL Database or environments without event_file access. Ring buffer can be slower with large buffers."
117 - },
118 - "error_info_function_enabled": {
119 - "ui:help": "When enabled, the error-info function becomes available in the Netdata dashboard. WARNING: error messages and query text may include sensitive literals."
120 - },
121 - "error_info_session_name": {
122 - "ui:help": "The Extended Events session must be created by an administrator and include an event_file target capturing sqlserver.error_reported with sql_text action."
123 - },
124 - "error_info_use_ring_buffer": {
125 - "ui:help": "Enable for Azure SQL Database or environments without event_file access. Ring buffer can be slower with large buffers."
152 + "functions": {
153 + "top_queries": {
154 + "disabled": {
155 + "ui:help": "WARNING: Query Store may contain unmasked PII (customer names, emails, IDs) in query text."
156 + },
157 + "time_window_days": {
158 + "ui:help": "Limits Query Store data to recent days. Lower values improve performance on busy servers."
159 + }
160 + },
161 + "deadlock_info": {
162 + "disabled": {
163 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals. Requires VIEW SERVER STATE permission."
164 + },
165 + "use_ring_buffer": {
166 + "ui:help": "Enable for Azure SQL Database or environments without event_file access. Ring buffer can be slower."
167 + }
168 + },
169 + "error_info": {
170 + "disabled": {
171 + "ui:help": "WARNING: Error messages and query text may contain unmasked sensitive literals. Requires VIEW SERVER STATE permission."
172 + },
173 + "session_name": {
174 + "ui:help": "The Extended Events session must be created by an administrator and include an event_file target capturing sqlserver.error_reported with sql_text action."
175 + },
176 + "use_ring_buffer": {
177 + "ui:help": "Enable for Azure SQL Database or environments without event_file access. Ring buffer can be slower."
178 + }
179 + }
180 },
181 "ui:flavour": "tabs",
182 "ui:options": {
@@ -133,15 +187,13 @@
187 "update_every",
188 "dsn",
189 "timeout",
136 - "vnode",
137 - "deadlock_info_function_enabled"
190 + "vnode"
191 ]
192 },
193 {
141 - "title": "Query Store",
194 + "title": "Functions",
195 "fields": [
143 - "query_store_function_enabled",
144 - "query_store_time_window_days"
196 + "functions"
197 ]
198 }
199 ]
src/go/plugin/go.d/collector/mssql/func_deadlock_info.go
+7 -9
@@ -246,7 +246,7 @@ func newFuncDeadlockInfo(r *funcRouter) *funcDeadlockInfo {
246 var _ funcapi.MethodHandler = (*funcDeadlockInfo)(nil)
247
248 func (f *funcDeadlockInfo) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
249 - if !f.router.collector.Config.GetDeadlockInfoFunctionEnabled() {
249 + if f.router.collector.Functions.DeadlockInfo.Disabled {
250 return nil, fmt.Errorf("deadlock-info function disabled in configuration")
251 }
252 return []funcapi.ParamConfig{}, nil
@@ -260,18 +260,16 @@ func (f *funcDeadlockInfo) Handle(ctx context.Context, method string, params fun
260 }
261 f.router.collector.db = db
262 }
263 - return f.collectData(ctx)
263 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.deadlockInfoTimeout())
264 + defer cancel()
265 + return f.collectData(queryCtx)
266 }
267
268 func (f *funcDeadlockInfo) Cleanup(ctx context.Context) {}
269
270 func (f *funcDeadlockInfo) collectData(ctx context.Context) *funcapi.FunctionResponse {
269 - if !f.router.collector.Config.GetDeadlockInfoFunctionEnabled() {
270 - return &funcapi.FunctionResponse{
271 - Status: 503,
272 - Message: "deadlock-info function has been disabled in configuration. " +
273 - "To enable, set deadlock_info_function_enabled: true in the MSSQL collector config.",
274 - }
271 + if f.router.collector.Functions.DeadlockInfo.Disabled {
272 + return funcapi.UnavailableResponse("deadlock-info function has been disabled in configuration")
273 }
274
275 deadlockTime, deadlockXML, err := f.queryLatestDeadlock(ctx)
@@ -343,7 +341,7 @@ func (f *funcDeadlockInfo) queryLatestDeadlock(ctx context.Context) (time.Time,
341 defer cancel()
342
343 query := querySystemHealthLatestDeadlockEventFile
346 - if f.router.collector.Config.GetDeadlockInfoUseRingBuffer() {
344 + if f.router.collector.Functions.DeadlockInfo.UseRingBuffer {
345 query = querySystemHealthLatestDeadlockRingBuffer
346 }
347
src/go/plugin/go.d/collector/mssql/func_deadlock_info_test.go
+13 -72
@@ -20,75 +20,14 @@ func newTestDeadlockHandler(c *Collector) *funcDeadlockInfo {
20 return &funcDeadlockInfo{router: r}
21 }
22
23 -func TestConfig_GetDeadlockInfoFunctionEnabled(t *testing.T) {
24 - tests := []struct {
25 - name string
26 - cfg Config
27 - want bool
28 - }{
29 - {
30 - name: "default enabled when unset",
31 - cfg: Config{},
32 - want: true,
33 - },
34 - {
35 - name: "explicitly enabled",
36 - cfg: Config{
37 - DeadlockInfoFunctionEnabled: boolPtr(true),
38 - },
39 - want: true,
40 - },
41 - {
42 - name: "explicitly disabled",
43 - cfg: Config{
44 - DeadlockInfoFunctionEnabled: boolPtr(false),
45 - },
46 - want: false,
47 - },
48 - }
49 -
50 - for _, tt := range tests {
51 - t.Run(tt.name, func(t *testing.T) {
52 - assert.Equal(t, tt.want, tt.cfg.GetDeadlockInfoFunctionEnabled())
53 - })
54 - }
23 +func TestConfig_FunctionsDisabledDefaults(t *testing.T) {
24 + cfg := Config{}
25 + assert.False(t, cfg.Functions.DeadlockInfo.Disabled, "deadlock_info should be enabled by default")
26 + assert.False(t, cfg.Functions.ErrorInfo.Disabled, "error_info should be enabled by default")
27 + assert.False(t, cfg.Functions.TopQueries.Disabled, "top_queries should be enabled by default")
28 }
29
57 -func TestConfig_GetErrorInfoFunctionEnabled(t *testing.T) {
58 - tests := []struct {
59 - name string
60 - cfg Config
61 - want bool
62 - }{
63 - {
64 - name: "default enabled when unset",
65 - cfg: Config{},
66 - want: true,
67 - },
68 - {
69 - name: "explicitly enabled",
70 - cfg: Config{
71 - ErrorInfoFunctionEnabled: boolPtr(true),
72 - },
73 - want: true,
74 - },
75 - {
76 - name: "explicitly disabled",
77 - cfg: Config{
78 - ErrorInfoFunctionEnabled: boolPtr(false),
79 - },
80 - want: false,
81 - },
82 - }
83 -
84 - for _, tt := range tests {
85 - t.Run(tt.name, func(t *testing.T) {
86 - assert.Equal(t, tt.want, tt.cfg.GetErrorInfoFunctionEnabled())
87 - })
88 - }
89 -}
90 -
91 -func TestConfig_GetErrorInfoSessionName(t *testing.T) {
30 +func TestConfig_ErrorInfoSessionName(t *testing.T) {
31 tests := []struct {
32 name string
33 cfg Config
@@ -102,7 +41,11 @@ func TestConfig_GetErrorInfoSessionName(t *testing.T) {
41 {
42 name: "explicit session name",
43 cfg: Config{
105 - ErrorInfoSessionName: "custom_errors",
44 + Functions: FunctionsConfig{
45 + ErrorInfo: ErrorInfoConfig{
46 + SessionName: "custom_errors",
47 + },
48 + },
49 },
50 want: "custom_errors",
51 },
@@ -110,7 +53,7 @@ func TestConfig_GetErrorInfoSessionName(t *testing.T) {
53
54 for _, tt := range tests {
55 t.Run(tt.name, func(t *testing.T) {
113 - assert.Equal(t, tt.want, tt.cfg.GetErrorInfoSessionName())
56 + assert.Equal(t, tt.want, tt.cfg.errorInfoSessionName())
57 })
58 }
59 }
@@ -277,7 +220,7 @@ func TestCollectDeadlockInfo_PermissionDenied(t *testing.T) {
220
221 func TestCollectDeadlockInfo_Disabled(t *testing.T) {
222 c := New()
280 - c.Config.DeadlockInfoFunctionEnabled = boolPtr(false)
223 + c.Config.Functions.DeadlockInfo.Disabled = true
224 handler := newTestDeadlockHandler(c)
225
226 resp := handler.collectData(context.Background())
@@ -341,8 +284,6 @@ func findTxn(txns []*mssqlDeadlockTxn, id string) *mssqlDeadlockTxn {
284 return nil
285 }
286
344 -func boolPtr(v bool) *bool { return &v }
345 -
287 const sampleDeadlockGraph = `
288 <deadlock>
289 <victim-list>
src/go/plugin/go.d/collector/mssql/func_error_info.go
+13 -14
@@ -150,7 +150,7 @@ func newFuncErrorInfo(r *funcRouter) *funcErrorInfo {
150 var _ funcapi.MethodHandler = (*funcErrorInfo)(nil)
151
152 func (f *funcErrorInfo) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
153 - if !f.router.collector.Config.GetErrorInfoFunctionEnabled() {
153 + if f.router.collector.Functions.ErrorInfo.Disabled {
154 return nil, fmt.Errorf("error-info function disabled in configuration")
155 }
156 return []funcapi.ParamConfig{}, nil
@@ -164,29 +164,28 @@ func (f *funcErrorInfo) Handle(ctx context.Context, method string, params funcap
164 }
165 f.router.collector.db = db
166 }
167 - return f.collectData(ctx)
167 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.errorInfoTimeout())
168 + defer cancel()
169 + return f.collectData(queryCtx)
170 }
171
172 func (f *funcErrorInfo) Cleanup(ctx context.Context) {}
173
174 func (f *funcErrorInfo) collectData(ctx context.Context) *funcapi.FunctionResponse {
173 - if !f.router.collector.Config.GetErrorInfoFunctionEnabled() {
174 - return &funcapi.FunctionResponse{
175 - Status: 503,
176 - Message: "error-info not enabled: function disabled in configuration. " +
177 - "To enable, set error_info_function_enabled: true in the MSSQL collector config.",
178 - }
175 + if f.router.collector.Functions.ErrorInfo.Disabled {
176 + return funcapi.UnavailableResponse("error-info function has been disabled in configuration")
177 }
178
181 - sessionName := f.router.collector.Config.GetErrorInfoSessionName()
182 - status, rows, err := f.router.collector.fetchMSSQLErrorRows(ctx, sessionName, f.router.collector.TopQueriesLimit)
179 + sessionName := f.router.collector.errorInfoSessionName()
180 + limit := f.router.collector.topQueriesLimit()
181 + status, rows, err := f.router.collector.fetchMSSQLErrorRows(ctx, sessionName, limit)
182 if err != nil {
183 if isDeadlockPermissionError(err) {
184 return &funcapi.FunctionResponse{Status: 403, Message: errorInfoPermissionMessage()}
185 }
186 if status == mssqlErrorAttrNotEnabled {
187 targetName := "event_file"
189 - if f.router.collector.Config.GetErrorInfoUseRingBuffer() {
188 + if f.router.collector.Functions.ErrorInfo.UseRingBuffer {
189 targetName = "ring_buffer"
190 }
191 return &funcapi.FunctionResponse{Status: 503, Message: fmt.Sprintf("error-info not enabled: Extended Events session not found or %s target missing", targetName)}
@@ -363,7 +362,7 @@ func nullableString(value string) any {
362 // A cleaner design would be a mssqlErrorData type on funcRouter that both handlers use.
363
364 func (c *Collector) collectMSSQLErrorDetails(ctx context.Context) (string, map[string]mssqlErrorRow) {
366 - status, rows, err := c.fetchMSSQLErrorRows(ctx, c.Config.GetErrorInfoSessionName(), c.TopQueriesLimit)
365 + status, rows, err := c.fetchMSSQLErrorRows(ctx, c.errorInfoSessionName(), c.topQueriesLimit())
366 if err != nil {
367 if status == mssqlErrorAttrNotEnabled {
368 return mssqlErrorAttrNotEnabled, nil
@@ -537,7 +536,7 @@ func (c *Collector) fetchMSSQLErrorRows(ctx context.Context, sessionName string,
536 defer cancel()
537
538 query := queryMSSQLErrorInfoEventFile
540 - if c.Config.GetErrorInfoUseRingBuffer() {
539 + if c.Functions.ErrorInfo.UseRingBuffer {
540 query = queryMSSQLErrorInfoRingBuffer
541 }
542
@@ -600,7 +599,7 @@ func (c *Collector) mssqlErrorSessionAvailable(ctx context.Context, sessionName
599 }
600
601 targetQuery := queryMSSQLErrorSessionHasEventFile
603 - if c.Config.GetErrorInfoUseRingBuffer() {
602 + if c.Functions.ErrorInfo.UseRingBuffer {
603 targetQuery = queryMSSQLErrorSessionHasRingBuffer
604 }
605
src/go/plugin/go.d/collector/mssql/func_top_queries.go
+9 -14
@@ -205,7 +205,9 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
205 }
206 switch method {
207 case topQueriesMethodID:
208 - return f.collectData(ctx, params.Column(topQueriesParamSort))
208 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
209 + defer cancel()
210 + return f.collectData(queryCtx, params.Column(topQueriesParamSort))
211 default:
212 return funcapi.NotFoundResponse(method)
213 }
@@ -215,8 +217,8 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
217 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
218
219 func (f *funcTopQueries) methodParams(ctx context.Context) ([]funcapi.ParamConfig, error) {
218 - if !f.router.collector.Config.GetQueryStoreFunctionEnabled() {
219 - return nil, fmt.Errorf("query store function disabled")
220 + if f.router.collector.Functions.TopQueries.Disabled {
221 + return nil, fmt.Errorf("top-queries function disabled in configuration")
222 }
223
224 availableCols, err := f.detectQueryStoreColumns(ctx)
@@ -234,12 +236,8 @@ func (f *funcTopQueries) methodParams(ctx context.Context) ([]funcapi.ParamConfi
236 }
237
238 func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
237 - if !f.router.collector.Config.GetQueryStoreFunctionEnabled() {
238 - return &funcapi.FunctionResponse{
239 - Status: 403,
240 - Message: "Query Store function has been disabled in configuration. " +
241 - "To enable, set query_store_function_enabled: true in the MSSQL collector config.",
242 - }
239 + if f.router.collector.Functions.TopQueries.Disabled {
240 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
241 }
242
243 availableCols, err := f.detectQueryStoreColumns(ctx)
@@ -260,11 +258,8 @@ func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *fu
258
259 validatedSortColumn := f.mapAndValidateSortColumn(sortColumn, cols)
260
263 - timeWindowDays := f.router.collector.Config.GetQueryStoreTimeWindowDays()
264 - limit := f.router.collector.TopQueriesLimit
265 - if limit <= 0 {
266 - limit = 500
267 - }
261 + timeWindowDays := f.router.collector.topQueriesTimeWindowDays()
262 + limit := f.router.collector.topQueriesLimit()
263 query := f.buildDynamicSQL(cols, validatedSortColumn, timeWindowDays, limit)
264
265 rows, err := f.router.collector.db.QueryContext(ctx, query)
src/go/plugin/go.d/collector/mssql/metadata.yaml
+63 -13
@@ -137,21 +137,65 @@ modules:
137 required: false
138 group: Target
139
140 - - name: query_store_function_enabled
141 - description: |
142 - Enable the Query Store function to expose top queries via Netdata Functions.
143 - **WARNING**: Query Store may contain unmasked literal values (customer names, emails, IDs).
144 - Only enable after ensuring proper access controls to the Netdata dashboard.
140 + - name: functions.top_queries.disabled
141 + description: Disable the [top-queries](#top-queries) function.
142 default_value: false
143 required: false
147 - group: Query Store
148 - - name: query_store_time_window_days
144 + group: Functions
145 + - name: functions.top_queries.timeout
146 + description: Query timeout for top-queries function (seconds). Uses collector timeout if not set.
147 + default_value: ""
148 + required: false
149 + group: Functions
150 + - name: functions.top_queries.limit
151 + description: Maximum number of queries to return in the top-queries response.
152 + default_value: 500
153 + required: false
154 + group: Functions
155 + - name: functions.top_queries.time_window_days
156 description: |
157 Number of days of Query Store data to analyze. Set to 0 to include all available data.
158 Smaller values improve query performance but show less history.
159 default_value: 7
160 required: false
154 - group: Query Store
161 + group: Functions
162 +
163 + - name: functions.deadlock_info.disabled
164 + description: Disable the [deadlock-info](#deadlock-info) function.
165 + default_value: false
166 + required: false
167 + group: Functions
168 + - name: functions.deadlock_info.timeout
169 + description: Query timeout for deadlock-info function (seconds). Uses collector timeout if not set.
170 + default_value: ""
171 + required: false
172 + group: Functions
173 + - name: functions.deadlock_info.use_ring_buffer
174 + description: "Use ring_buffer instead of event_file for system_health session.<br/><br/>WARNING: Not recommended for production:<br/>• Data cleared on failover/restart<br/>• 4 MB capacity limit<br/>• High CPU load during queries<br/><br/>Use only for Azure SQL Database without Blob Storage or testing."
175 + default_value: false
176 + required: false
177 + group: Functions
178 +
179 + - name: functions.error_info.disabled
180 + description: Disable the [error-info](#error-info) function.
181 + default_value: false
182 + required: false
183 + group: Functions
184 + - name: functions.error_info.timeout
185 + description: Query timeout for error-info function (seconds). Uses collector timeout if not set.
186 + default_value: ""
187 + required: false
188 + group: Functions
189 + - name: functions.error_info.session_name
190 + description: "Extended Events session name capturing error_reported events.<br/>Must be created by administrator with event_file (recommended) or ring_buffer target."
191 + default_value: netdata_errors
192 + required: false
193 + group: Functions
194 + - name: functions.error_info.use_ring_buffer
195 + description: "Use ring_buffer instead of event_file for error events.<br/><br/>WARNING: Not recommended for production:<br/>• Data cleared on failover/restart<br/>• 4 MB capacity limit<br/>• High CPU load during queries<br/><br/>Use only for Azure SQL Database without Blob Storage or testing."
196 + default_value: false
197 + required: false
198 + group: Functions
199
200 - name: vnode
201 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
@@ -199,18 +243,24 @@ modules:
243
244 - name: development
245 dsn: "sqlserver://netdata_user:password@dev-sql:1433"
202 - - name: With Query Store function
246 + - name: With custom function settings
247 description: |
204 - Enable the Query Store function to view top queries in the Netdata dashboard.
248 + Configure function-specific settings like timeouts and limits.
249
250 > **Warning**: Query Store may contain unmasked literal values (PII).
207 - > Only enable in environments with proper access controls.
251 + > Disable functions if not needed or ensure proper access controls.
252 config: |
253 jobs:
254 - name: local
255 dsn: "sqlserver://netdata_user:password@localhost:1433"
212 - query_store_function_enabled: true
213 - query_store_time_window_days: 7
256 + functions:
257 + top_queries:
258 + limit: 100
259 + time_window_days: 7
260 + deadlock_info:
261 + use_ring_buffer: true
262 + error_info:
263 + session_name: custom_errors
264 troubleshooting:
265 problems:
266 list:
src/go/plugin/go.d/collector/mysql/collector.go
+62 -25
@@ -37,6 +37,11 @@ func New() *Collector {
37 Config: Config{
38 DSN: "root@tcp(localhost:3306)/",
39 Timeout: confopt.Duration(time.Second),
40 + Functions: FunctionsConfig{
41 + TopQueries: TopQueriesConfig{
42 + Limit: 500,
43 + },
44 + },
45 },
46
47 charts: baseCharts.Copy(),
@@ -63,15 +68,63 @@ func New() *Collector {
68 }
69
70 type Config struct {
66 - Vnode string `yaml:"vnode,omitempty" json:"vnode"`
67 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
68 - AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
69 - DSN string `yaml:"dsn" json:"dsn"`
70 - MyCNF string `yaml:"my.cnf,omitempty" json:"my.cnf"`
71 - Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
72 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
73 - DeadlockInfoFunctionEnabled *bool `yaml:"deadlock_info_function_enabled,omitempty" json:"deadlock_info_function_enabled,omitempty"`
74 - ErrorInfoFunctionEnabled *bool `yaml:"error_info_function_enabled,omitempty" json:"error_info_function_enabled,omitempty"`
71 + Vnode string `yaml:"vnode,omitempty" json:"vnode"`
72 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
73 + AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
74 + DSN string `yaml:"dsn" json:"dsn"`
75 + MyCNF string `yaml:"my.cnf,omitempty" json:"my.cnf"`
76 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
77 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
78 +}
79 +
80 +type FunctionsConfig struct {
81 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
82 + DeadlockInfo DeadlockInfoConfig `yaml:"deadlock_info,omitempty" json:"deadlock_info"`
83 + ErrorInfo ErrorInfoConfig `yaml:"error_info,omitempty" json:"error_info"`
84 +}
85 +
86 +type TopQueriesConfig struct {
87 + Disabled bool `yaml:"disabled" json:"disabled"`
88 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
89 + Limit int `yaml:"limit,omitempty" json:"limit"`
90 +}
91 +
92 +type DeadlockInfoConfig struct {
93 + Disabled bool `yaml:"disabled" json:"disabled"`
94 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
95 +}
96 +
97 +type ErrorInfoConfig struct {
98 + Disabled bool `yaml:"disabled" json:"disabled"`
99 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
100 +}
101 +
102 +func (c Config) topQueriesTimeout() time.Duration {
103 + if c.Functions.TopQueries.Timeout == 0 {
104 + return c.Timeout.Duration()
105 + }
106 + return c.Functions.TopQueries.Timeout.Duration()
107 +}
108 +
109 +func (c Config) topQueriesLimit() int {
110 + if c.Functions.TopQueries.Limit <= 0 {
111 + return 500
112 + }
113 + return c.Functions.TopQueries.Limit
114 +}
115 +
116 +func (c Config) deadlockInfoTimeout() time.Duration {
117 + if c.Functions.DeadlockInfo.Timeout == 0 {
118 + return c.Timeout.Duration()
119 + }
120 + return c.Functions.DeadlockInfo.Timeout.Duration()
121 +}
122 +
123 +func (c Config) errorInfoTimeout() time.Duration {
124 + if c.Functions.ErrorInfo.Timeout == 0 {
125 + return c.Timeout.Duration()
126 + }
127 + return c.Functions.ErrorInfo.Timeout.Duration()
128 }
129
130 type Collector struct {
@@ -122,22 +175,6 @@ func (c *Collector) Configuration() any {
175 return c.Config
176 }
177
125 -// GetDeadlockInfoFunctionEnabled returns whether the deadlock-info function is enabled (default: true).
126 -func (c *Config) GetDeadlockInfoFunctionEnabled() bool {
127 - if c.DeadlockInfoFunctionEnabled == nil {
128 - return true
129 - }
130 - return *c.DeadlockInfoFunctionEnabled
131 -}
132 -
133 -// GetErrorInfoFunctionEnabled returns whether the error-info function is enabled (default: true).
134 -func (c *Config) GetErrorInfoFunctionEnabled() bool {
135 - if c.ErrorInfoFunctionEnabled == nil {
136 - return true
137 - }
138 - return *c.ErrorInfoFunctionEnabled
139 -}
140 -
178 func (c *Collector) Init(context.Context) error {
179 if c.MyCNF != "" {
180 dsn, err := dsnFromFile(c.MyCNF)
src/go/plugin/go.d/collector/mysql/config_schema.json
+108 -23
@@ -41,25 +41,77 @@
41 "description": "Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).",
42 "type": "string"
43 },
44 - "top_queries_limit": {
45 - "title": "Top Queries Limit",
46 - "description": "Maximum number of queries to return in the top-queries function response.",
47 - "type": "integer",
48 - "minimum": 1,
49 - "maximum": 5000,
50 - "default": 500
51 - },
52 - "deadlock_info_function_enabled": {
53 - "title": "Enable Deadlock Info Function",
54 - "description": "Enable the deadlock-info function. WARNING: query text may contain unmasked sensitive literals (PII). Only enable after ensuring proper access controls to the Netdata dashboard. This function reads SHOW ENGINE INNODB STATUS and may require PROCESS privilege.",
55 - "type": "boolean",
56 - "default": true
57 - },
58 - "error_info_function_enabled": {
59 - "title": "Enable Error Info Function",
60 - "description": "Enable the error-info function. WARNING: error messages and query text may contain unmasked sensitive literals (PII). This function reads Performance Schema statement history tables; ensure proper access controls to the Netdata dashboard.",
61 - "type": "boolean",
62 - "default": true
44 + "functions": {
45 + "title": "Functions",
46 + "description": "Configuration for Netdata functions exposed by this collector.",
47 + "type": "object",
48 + "properties": {
49 + "top_queries": {
50 + "title": "Top Queries",
51 + "description": "Configuration for the top-queries function.",
52 + "type": "object",
53 + "properties": {
54 + "disabled": {
55 + "title": "Disabled",
56 + "description": "Disable the top-queries function.",
57 + "type": "boolean",
58 + "default": false
59 + },
60 + "timeout": {
61 + "title": "Timeout",
62 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
63 + "type": "number",
64 + "minimum": 0
65 + },
66 + "limit": {
67 + "title": "Limit",
68 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
69 + "type": "integer",
70 + "minimum": 0,
71 + "maximum": 5000,
72 + "default": 500
73 + }
74 + }
75 + },
76 + "deadlock_info": {
77 + "title": "Deadlock Info",
78 + "description": "Configuration for the deadlock-info function.",
79 + "type": "object",
80 + "properties": {
81 + "disabled": {
82 + "title": "Disabled",
83 + "description": "Disable the deadlock-info function.",
84 + "type": "boolean",
85 + "default": false
86 + },
87 + "timeout": {
88 + "title": "Timeout",
89 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
90 + "type": "number",
91 + "minimum": 0
92 + }
93 + }
94 + },
95 + "error_info": {
96 + "title": "Error Info",
97 + "description": "Configuration for the error-info function.",
98 + "type": "object",
99 + "properties": {
100 + "disabled": {
101 + "title": "Disabled",
102 + "description": "Disable the error-info function.",
103 + "type": "boolean",
104 + "default": false
105 + },
106 + "timeout": {
107 + "title": "Timeout",
108 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
109 + "type": "number",
110 + "minimum": 0
111 + }
112 + }
113 + }
114 + }
115 }
116 },
117 "required": [
@@ -82,11 +134,44 @@
134 "timeout": {
135 "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
136 },
85 - "deadlock_info_function_enabled": {
86 - "ui:help": "When enabled, the deadlock-info function becomes available in the Netdata dashboard. WARNING: query text may contain unmasked sensitive literals; restrict dashboard access."
137 + "functions": {
138 + "top_queries": {
139 + "disabled": {
140 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals (PII)."
141 + }
142 + },
143 + "deadlock_info": {
144 + "disabled": {
145 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals. Requires PROCESS privilege."
146 + }
147 + },
148 + "error_info": {
149 + "disabled": {
150 + "ui:help": "WARNING: Error messages and query text may contain unmasked sensitive literals."
151 + }
152 + }
153 },
88 - "error_info_function_enabled": {
89 - "ui:help": "When enabled, the error-info function becomes available in the Netdata dashboard. WARNING: error messages and query text may include sensitive literals."
154 + "ui:flavour": "tabs",
155 + "ui:options": {
156 + "tabs": [
157 + {
158 + "title": "Base",
159 + "fields": [
160 + "update_every",
161 + "dsn",
162 + "timeout",
163 + "my.cnf",
164 + "vnode",
165 + "autodetection_retry"
166 + ]
167 + },
168 + {
169 + "title": "Functions",
170 + "fields": [
171 + "functions"
172 + ]
173 + }
174 + ]
175 }
176 }
177 }
src/go/plugin/go.d/collector/mysql/func_deadlock_info.go
+6 -8
@@ -265,7 +265,7 @@ func newFuncDeadlockInfo(r *funcRouter) *funcDeadlockInfo {
265 var _ funcapi.MethodHandler = (*funcDeadlockInfo)(nil)
266
267 func (f *funcDeadlockInfo) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
268 - if !f.router.collector.Config.GetDeadlockInfoFunctionEnabled() {
268 + if f.router.collector.Functions.DeadlockInfo.Disabled {
269 return nil, fmt.Errorf("deadlock-info function disabled in configuration")
270 }
271 return []funcapi.ParamConfig{}, nil
@@ -277,18 +277,16 @@ func (f *funcDeadlockInfo) Handle(ctx context.Context, method string, params fun
277 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
278 }
279 }
280 - return f.collectData(ctx)
280 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.deadlockInfoTimeout())
281 + defer cancel()
282 + return f.collectData(queryCtx)
283 }
284
285 func (f *funcDeadlockInfo) Cleanup(ctx context.Context) {}
286
287 func (f *funcDeadlockInfo) collectData(ctx context.Context) *funcapi.FunctionResponse {
286 - if !f.router.collector.Config.GetDeadlockInfoFunctionEnabled() {
287 - return &funcapi.FunctionResponse{
288 - Status: 503,
289 - Message: "deadlock-info function has been disabled in configuration. " +
290 - "To enable, set deadlock_info_function_enabled: true in the MySQL collector config.",
291 - }
288 + if f.router.collector.Functions.DeadlockInfo.Disabled {
289 + return funcapi.UnavailableResponse("deadlock-info function has been disabled in configuration")
290 }
291
292 statusText, err := f.queryInnoDBStatus(ctx)
src/go/plugin/go.d/collector/mysql/func_deadlock_info_test.go
+6 -71
@@ -15,72 +15,11 @@ import (
15 "github.com/stretchr/testify/require"
16 )
17
18 -func TestConfig_GetDeadlockInfoFunctionEnabled(t *testing.T) {
19 - tests := []struct {
20 - name string
21 - cfg Config
22 - expected bool
23 - }{
24 - {
25 - name: "default nil pointer enables function",
26 - cfg: Config{},
27 - expected: true,
28 - },
29 - {
30 - name: "explicit true enables function",
31 - cfg: Config{
32 - DeadlockInfoFunctionEnabled: boolPtr(true),
33 - },
34 - expected: true,
35 - },
36 - {
37 - name: "explicit false disables function",
38 - cfg: Config{
39 - DeadlockInfoFunctionEnabled: boolPtr(false),
40 - },
41 - expected: false,
42 - },
43 - }
44 -
45 - for _, tt := range tests {
46 - t.Run(tt.name, func(t *testing.T) {
47 - assert.Equal(t, tt.expected, tt.cfg.GetDeadlockInfoFunctionEnabled())
48 - })
49 - }
50 -}
51 -
52 -func TestConfig_GetErrorInfoFunctionEnabled(t *testing.T) {
53 - tests := []struct {
54 - name string
55 - cfg Config
56 - expected bool
57 - }{
58 - {
59 - name: "default nil pointer enables function",
60 - cfg: Config{},
61 - expected: true,
62 - },
63 - {
64 - name: "explicit true enables function",
65 - cfg: Config{
66 - ErrorInfoFunctionEnabled: boolPtr(true),
67 - },
68 - expected: true,
69 - },
70 - {
71 - name: "explicit false disables function",
72 - cfg: Config{
73 - ErrorInfoFunctionEnabled: boolPtr(false),
74 - },
75 - expected: false,
76 - },
77 - }
78 -
79 - for _, tt := range tests {
80 - t.Run(tt.name, func(t *testing.T) {
81 - assert.Equal(t, tt.expected, tt.cfg.GetErrorInfoFunctionEnabled())
82 - })
83 - }
18 +func TestConfig_FunctionsDisabledDefaults(t *testing.T) {
19 + cfg := Config{}
20 + assert.False(t, cfg.Functions.DeadlockInfo.Disabled, "deadlock_info should be enabled by default")
21 + assert.False(t, cfg.Functions.ErrorInfo.Disabled, "error_info should be enabled by default")
22 + assert.False(t, cfg.Functions.TopQueries.Disabled, "top_queries should be enabled by default")
23 }
24
25 func TestParseInnoDBDeadlock_WithDeadlock(t *testing.T) {
@@ -347,7 +286,7 @@ func TestFuncDeadlockInfo_collectData_PermissionDenied(t *testing.T) {
286
287 func TestFuncDeadlockInfo_collectData_Disabled(t *testing.T) {
288 c := New()
350 - c.Config.DeadlockInfoFunctionEnabled = boolPtr(false)
289 + c.Config.Functions.DeadlockInfo.Disabled = true
290 handler := newTestDeadlockHandler(c)
291
292 resp := handler.collectData(context.Background())
@@ -377,10 +316,6 @@ func TestBuildDeadlockRows(t *testing.T) {
316 assert.True(t, hasDatabase, "expected at least one row with database populated")
317 }
318
380 -func boolPtr(v bool) *bool {
381 - return &v
382 -}
383 -
319 const sampleDeadlockStatus = `
320 ------------------------
321 LATEST DETECTED DEADLOCK
src/go/plugin/go.d/collector/mysql/func_error_info.go
+7 -12
@@ -130,7 +130,7 @@ func newFuncErrorInfo(r *funcRouter) *funcErrorInfo {
130 var _ funcapi.MethodHandler = (*funcErrorInfo)(nil)
131
132 func (f *funcErrorInfo) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
133 - if !f.router.collector.Config.GetErrorInfoFunctionEnabled() {
133 + if f.router.collector.Functions.ErrorInfo.Disabled {
134 return nil, fmt.Errorf("error-info function disabled in configuration")
135 }
136 return []funcapi.ParamConfig{}, nil
@@ -142,18 +142,16 @@ func (f *funcErrorInfo) Handle(ctx context.Context, method string, params funcap
142 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
143 }
144 }
145 - return f.collectData(ctx)
145 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.errorInfoTimeout())
146 + defer cancel()
147 + return f.collectData(queryCtx)
148 }
149
150 func (f *funcErrorInfo) Cleanup(ctx context.Context) {}
151
152 func (f *funcErrorInfo) collectData(ctx context.Context) *funcapi.FunctionResponse {
151 - if !f.router.collector.Config.GetErrorInfoFunctionEnabled() {
152 - return &funcapi.FunctionResponse{
153 - Status: 503,
154 - Message: "error-info not enabled: function disabled in configuration. " +
155 - "To enable, set error_info_function_enabled: true in the MySQL collector config.",
156 - }
153 + if f.router.collector.Functions.ErrorInfo.Disabled {
154 + return funcapi.UnavailableResponse("error-info function has been disabled in configuration")
155 }
156
157 available, err := f.checkPerformanceSchema(ctx)
@@ -179,10 +177,7 @@ func (f *funcErrorInfo) collectData(ctx context.Context) *funcapi.FunctionRespon
177 return &funcapi.FunctionResponse{Status: 503, Message: msg}
178 }
179
182 - limit := f.router.collector.TopQueriesLimit
183 - if limit <= 0 {
184 - limit = 500
185 - }
180 + limit := f.router.collector.topQueriesLimit()
181
182 rows, err := f.router.collector.fetchMySQLErrorRows(ctx, source, nil, limit)
183 if err != nil {
src/go/plugin/go.d/collector/mysql/func_top_queries.go
+10 -6
@@ -174,6 +174,9 @@ var _ funcapi.MethodHandler = (*funcTopQueries)(nil)
174
175 // MethodParams implements funcapi.MethodHandler.
176 func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
177 + if f.router.collector.Functions.TopQueries.Disabled {
178 + return nil, fmt.Errorf("top-queries function disabled in configuration")
179 + }
180 if f.router.collector.db == nil {
181 return nil, fmt.Errorf("collector is still initializing")
182 }
@@ -187,13 +190,18 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
190
191 // Handle implements funcapi.MethodHandler.
192 func (f *funcTopQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
193 + if f.router.collector.Functions.TopQueries.Disabled {
194 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
195 + }
196 if f.router.collector.db == nil {
197 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
198 }
199
200 switch method {
201 case topQueriesMethodID:
196 - return f.collectData(ctx, params.Column(topQueriesParamSort))
202 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
203 + defer cancel()
204 + return f.collectData(queryCtx, params.Column(topQueriesParamSort))
205 default:
206 return funcapi.NotFoundResponse(method)
207 }
@@ -262,11 +270,7 @@ func (f *funcTopQueries) collectData(ctx context.Context, sortColumn string) *fu
270 // Validate and map sort column
271 dbSortColumn := f.mapAndValidateSortColumn(sortColumn, availableCols)
272
265 - // Get query limit (default 500)
266 - limit := f.router.collector.TopQueriesLimit
267 - if limit <= 0 {
268 - limit = 500
269 - }
273 + limit := f.router.collector.topQueriesLimit()
274
275 // Build and execute query
276 query := f.buildDynamicSQL(cols, dbSortColumn, limit)
src/go/plugin/go.d/collector/mysql/metadata.yaml
+38
@@ -131,6 +131,44 @@ modules:
131 required: false
132 group: Target
133
134 + - name: functions.top_queries.disabled
135 + description: Disable the [top-queries](#top-queries) function.
136 + default_value: false
137 + required: false
138 + group: Functions
139 + - name: functions.top_queries.timeout
140 + description: Query timeout (seconds). Uses collector timeout if not set.
141 + default_value: ""
142 + required: false
143 + group: Functions
144 + - name: functions.top_queries.limit
145 + description: Maximum number of queries to return.
146 + default_value: 500
147 + required: false
148 + group: Functions
149 +
150 + - name: functions.deadlock_info.disabled
151 + description: Disable the [deadlock-info](#deadlock-info) function.
152 + default_value: false
153 + required: false
154 + group: Functions
155 + - name: functions.deadlock_info.timeout
156 + description: Query timeout (seconds). Uses collector timeout if not set.
157 + default_value: ""
158 + required: false
159 + group: Functions
160 +
161 + - name: functions.error_info.disabled
162 + description: Disable the [error-info](#error-info) function.
163 + default_value: false
164 + required: false
165 + group: Functions
166 + - name: functions.error_info.timeout
167 + description: Query timeout (seconds). Uses collector timeout if not set.
168 + default_value: ""
169 + required: false
170 + group: Functions
171 +
172 - name: vnode
173 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
174 default_value: ""
src/go/plugin/go.d/collector/mysql/testdata/config.json
+16 -1
@@ -4,5 +4,20 @@
4 "autodetection_retry": 123,
5 "dsn": "ok",
6 "my.cnf": "ok",
7 - "timeout": 123.123
7 + "timeout": 123.123,
8 + "functions": {
9 + "top_queries": {
10 + "disabled": true,
11 + "timeout": 123.123,
12 + "limit": 123
13 + },
14 + "deadlock_info": {
15 + "disabled": true,
16 + "timeout": 123.123
17 + },
18 + "error_info": {
19 + "disabled": true,
20 + "timeout": 123.123
21 + }
22 + }
23 }
src/go/plugin/go.d/collector/mysql/testdata/config.yaml
+11
@@ -4,3 +4,14 @@ autodetection_retry: 123
4 dsn: "ok"
5 my.cnf: "ok"
6 timeout: 123.123
7 +functions:
8 + top_queries:
9 + disabled: yes
10 + timeout: 123.123
11 + limit: 123
12 + deadlock_info:
13 + disabled: yes
14 + timeout: 123.123
15 + error_info:
16 + disabled: yes
17 + timeout: 123.123
src/go/plugin/go.d/collector/oracledb/collector.go
+55 -2
@@ -30,7 +30,15 @@ func init() {
30 func New() *Collector {
31 return &Collector{
32 Config: Config{
33 - Timeout: confopt.Duration(time.Second * 2),
33 + Timeout: confopt.Duration(time.Second * 2),
34 + Functions: FunctionsConfig{
35 + TopQueries: TopQueriesConfig{
36 + Limit: 500,
37 + },
38 + RunningQueries: RunningQueriesConfig{
39 + Limit: 500,
40 + },
41 + },
42 charts: globalCharts.Copy(),
43 seenTablespaces: make(map[string]bool),
44 seenWaitClasses: make(map[string]bool),
@@ -44,7 +52,7 @@ type Config struct {
52 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
53 DSN string `json:"dsn" yaml:"dsn"`
54 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
47 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
55 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
56
57 charts *module.Charts
58
@@ -54,6 +62,51 @@ type Config struct {
62 seenWaitClasses map[string]bool
63 }
64
65 +type FunctionsConfig struct {
66 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
67 + RunningQueries RunningQueriesConfig `yaml:"running_queries,omitempty" json:"running_queries"`
68 +}
69 +
70 +type TopQueriesConfig struct {
71 + Disabled bool `yaml:"disabled" json:"disabled"`
72 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
73 + Limit int `yaml:"limit,omitempty" json:"limit"`
74 +}
75 +
76 +type RunningQueriesConfig struct {
77 + Disabled bool `yaml:"disabled" json:"disabled"`
78 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
79 + Limit int `yaml:"limit,omitempty" json:"limit"`
80 +}
81 +
82 +func (c Config) topQueriesTimeout() time.Duration {
83 + if c.Functions.TopQueries.Timeout == 0 {
84 + return c.Timeout.Duration()
85 + }
86 + return c.Functions.TopQueries.Timeout.Duration()
87 +}
88 +
89 +func (c Config) topQueriesLimit() int {
90 + if c.Functions.TopQueries.Limit <= 0 {
91 + return 500
92 + }
93 + return c.Functions.TopQueries.Limit
94 +}
95 +
96 +func (c Config) runningQueriesTimeout() time.Duration {
97 + if c.Functions.RunningQueries.Timeout == 0 {
98 + return c.Timeout.Duration()
99 + }
100 + return c.Functions.RunningQueries.Timeout.Duration()
101 +}
102 +
103 +func (c Config) runningQueriesLimit() int {
104 + if c.Functions.RunningQueries.Limit <= 0 {
105 + return 500
106 + }
107 + return c.Functions.RunningQueries.Limit
108 +}
109 +
110 type Collector struct {
111 module.Base
112 Config `yaml:",inline" json:""`
src/go/plugin/go.d/collector/oracledb/config_schema.json
+94 -8
@@ -31,18 +31,71 @@
31 "minimum": 0.5,
32 "default": 1
33 },
34 - "top_queries_limit": {
35 - "title": "Top Queries Limit",
36 - "description": "Maximum number of queries to return in the top-queries and running-queries function responses.",
37 - "type": "integer",
38 - "minimum": 1,
39 - "maximum": 5000,
40 - "default": 500
41 - },
34 "vnode": {
35 "title": "Vnode",
36 "description": "Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).",
37 "type": "string"
38 + },
39 + "functions": {
40 + "title": "Functions",
41 + "description": "Configuration for Netdata functions exposed by this collector.",
42 + "type": "object",
43 + "properties": {
44 + "top_queries": {
45 + "title": "Top Queries",
46 + "description": "Configuration for the top-queries function.",
47 + "type": "object",
48 + "properties": {
49 + "disabled": {
50 + "title": "Disabled",
51 + "description": "Disable the top-queries function.",
52 + "type": "boolean",
53 + "default": false
54 + },
55 + "timeout": {
56 + "title": "Timeout",
57 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
58 + "type": "number",
59 + "minimum": 0
60 + },
61 + "limit": {
62 + "title": "Limit",
63 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
64 + "type": "integer",
65 + "minimum": 0,
66 + "maximum": 5000,
67 + "default": 500
68 + }
69 + }
70 + },
71 + "running_queries": {
72 + "title": "Running Queries",
73 + "description": "Configuration for the running-queries function.",
74 + "type": "object",
75 + "properties": {
76 + "disabled": {
77 + "title": "Disabled",
78 + "description": "Disable the running-queries function.",
79 + "type": "boolean",
80 + "default": false
81 + },
82 + "timeout": {
83 + "title": "Timeout",
84 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
85 + "type": "number",
86 + "minimum": 0
87 + },
88 + "limit": {
89 + "title": "Limit",
90 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
91 + "type": "integer",
92 + "minimum": 0,
93 + "maximum": 5000,
94 + "default": 500
95 + }
96 + }
97 + }
98 + }
99 }
100 },
101 "required": [
@@ -53,6 +106,27 @@
106 "uiOptions": {
107 "fullPage": true
108 },
109 + "ui:flavour": "tabs",
110 + "ui:options": {
111 + "tabs": [
112 + {
113 + "title": "Base",
114 + "fields": [
115 + "update_every",
116 + "autodetection_retry",
117 + "dsn",
118 + "timeout",
119 + "vnode"
120 + ]
121 + },
122 + {
123 + "title": "Functions",
124 + "fields": [
125 + "functions"
126 + ]
127 + }
128 + ]
129 + },
130 "vnode": {
131 "ui:placeholder": "To use this option, first create a Virtual Node and then reference its name here."
132 },
@@ -65,6 +139,18 @@
139 },
140 "timeout": {
141 "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
142 + },
143 + "functions": {
144 + "top_queries": {
145 + "disabled": {
146 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals (PII)."
147 + }
148 + },
149 + "running_queries": {
150 + "disabled": {
151 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals (PII)."
152 + }
153 + }
154 }
155 }
156 }
src/go/plugin/go.d/collector/oracledb/func_router.go
+1 -4
@@ -52,10 +52,7 @@ func (r *funcRouter) Cleanup(ctx context.Context) {
52 }
53
54 func (r *funcRouter) topQueriesLimit() int {
55 - if r.collector.TopQueriesLimit > 0 {
56 - return r.collector.TopQueriesLimit
57 - }
58 - return 500
55 + return r.collector.topQueriesLimit()
56 }
57
58 func oracledbMethods() []funcapi.MethodConfig {
src/go/plugin/go.d/collector/oracledb/func_running_queries.go
+12 -3
@@ -72,19 +72,28 @@ func newFuncRunningQueries(r *funcRouter) *funcRunningQueries {
72 var _ funcapi.MethodHandler = (*funcRunningQueries)(nil)
73
74 func (f *funcRunningQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
75 + if f.router.collector.Functions.RunningQueries.Disabled {
76 + return nil, fmt.Errorf("running-queries function disabled in configuration")
77 + }
78 return []funcapi.ParamConfig{funcapi.BuildSortParam(runningQueriesColumns)}, nil
79 }
80
81 func (f *funcRunningQueries) Cleanup(ctx context.Context) {}
82
83 func (f *funcRunningQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
84 + if f.router.collector.Functions.RunningQueries.Disabled {
85 + return funcapi.UnavailableResponse("running-queries function has been disabled in configuration")
86 + }
87 if f.router.collector.db == nil {
88 if err := f.router.collector.openConnection(); err != nil {
89 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
90 }
91 }
92
87 - limit := f.router.topQueriesLimit()
93 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.runningQueriesTimeout())
94 + defer cancel()
95 +
96 + limit := f.router.collector.runningQueriesLimit()
97
98 sortColumn := f.resolveSortColumn(params.Column("__sort"))
99 if sortColumn == "" {
@@ -103,9 +112,9 @@ ORDER BY %s DESC NULLS LAST
112 FETCH FIRST %d ROWS ONLY
113 `, f.buildSelectClause(), sortColumn, limit)
114
106 - rows, err := f.router.collector.db.QueryContext(ctx, query)
115 + rows, err := f.router.collector.db.QueryContext(queryCtx, query)
116 if err != nil {
108 - if ctx.Err() == context.DeadlineExceeded {
117 + if queryCtx.Err() == context.DeadlineExceeded {
118 return funcapi.ErrorResponse(504, "query timed out")
119 }
120 return funcapi.InternalErrorResponse("running queries query failed: %v", err)
src/go/plugin/go.d/collector/oracledb/func_top_queries.go
+12 -3
@@ -83,6 +83,9 @@ func newFuncTopQueries(r *funcRouter) *funcTopQueries {
83 var _ funcapi.MethodHandler = (*funcTopQueries)(nil)
84
85 func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
86 + if f.router.collector.Functions.TopQueries.Disabled {
87 + return nil, fmt.Errorf("top-queries function disabled in configuration")
88 + }
89 cols := topQueriesColumns
90 if f.router.collector.db != nil {
91 cols = f.layout(ctx).cols
@@ -93,13 +96,19 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
96 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
97
98 func (f *funcTopQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
99 + if f.router.collector.Functions.TopQueries.Disabled {
100 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
101 + }
102 if f.router.collector.db == nil {
103 if err := f.router.collector.openConnection(); err != nil {
104 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
105 }
106 }
107
102 - layout := f.layout(ctx)
108 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
109 + defer cancel()
110 +
111 + layout := f.layout(queryCtx)
112 cols := layout.cols
113 limit := f.router.topQueriesLimit()
114
@@ -121,9 +130,9 @@ ORDER BY %s DESC NULLS LAST
130 FETCH FIRST %d ROWS ONLY
131 `, f.buildSelectClause(cols), joinClause, sortColumn, limit)
132
124 - rows, err := f.router.collector.db.QueryContext(ctx, query)
133 + rows, err := f.router.collector.db.QueryContext(queryCtx, query)
134 if err != nil {
126 - if ctx.Err() == context.DeadlineExceeded {
135 + if queryCtx.Err() == context.DeadlineExceeded {
136 return funcapi.ErrorResponse(504, "query timed out")
137 }
138 return funcapi.InternalErrorResponse("top queries query failed: %v", err)
src/go/plugin/go.d/collector/oracledb/metadata.yaml
+29 -3
@@ -106,11 +106,37 @@ modules:
106 default_value: 1
107 required: false
108 group: Target
109 - - name: top_queries_limit
110 - description: Maximum number of rows returned by the `top-queries` and `running-queries` functions.
109 + - name: functions.top_queries.disabled
110 + description: Disable the [top-queries](#top-queries) function.
111 + default_value: false
112 + required: false
113 + group: Functions
114 + - name: functions.top_queries.timeout
115 + description: Query timeout (seconds). Uses collector timeout if not set.
116 + default_value: ""
117 + required: false
118 + group: Functions
119 + - name: functions.top_queries.limit
120 + description: Maximum number of queries to return.
121 + default_value: 500
122 + required: false
123 + group: Functions
124 +
125 + - name: functions.running_queries.disabled
126 + description: Disable the [running-queries](#running-queries) function.
127 + default_value: false
128 + required: false
129 + group: Functions
130 + - name: functions.running_queries.timeout
131 + description: Query timeout (seconds). Uses collector timeout if not set.
132 + default_value: ""
133 + required: false
134 + group: Functions
135 + - name: functions.running_queries.limit
136 + description: Maximum number of queries to return.
137 default_value: 500
138 required: false
113 - group: Limits
139 + group: Functions
140
141 - name: vnode
142 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
src/go/plugin/go.d/collector/oracledb/testdata/config.json
+13 -1
@@ -3,5 +3,17 @@
3 "update_every": 123,
4 "autodetection_retry": 123,
5 "dsn": "ok",
6 - "timeout": 123.123
6 + "timeout": 123.123,
7 + "functions": {
8 + "top_queries": {
9 + "disabled": true,
10 + "timeout": 123.123,
11 + "limit": 123
12 + },
13 + "running_queries": {
14 + "disabled": true,
15 + "timeout": 123.123,
16 + "limit": 123
17 + }
18 + }
19 }
src/go/plugin/go.d/collector/oracledb/testdata/config.yaml
+9
@@ -3,3 +3,12 @@ update_every: 123
3 autodetection_retry: 123
4 dsn: "ok"
5 timeout: 123.123
6 +functions:
7 + top_queries:
8 + disabled: yes
9 + timeout: 123.123
10 + limit: 123
11 + running_queries:
12 + disabled: yes
13 + timeout: 123.123
14 + limit: 123
src/go/plugin/go.d/collector/postgres/collector.go
+30 -1
@@ -43,6 +43,11 @@ func New() *Collector {
43 // https://discord.com/channels/847502280503590932/1022693928874549368
44 MaxDBTables: 50,
45 MaxDBIndexes: 250,
46 + Functions: FunctionsConfig{
47 + TopQueries: TopQueriesConfig{
48 + Limit: 500,
49 + },
50 + },
51 },
52 charts: baseCharts.Copy(),
53 dbConns: make(map[string]*dbConn),
@@ -71,7 +76,31 @@ type Config struct {
76 QueryTimeHistogram []float64 `yaml:"query_time_histogram,omitempty" json:"query_time_histogram"`
77 MaxDBTables int64 `yaml:"max_db_tables" json:"max_db_tables"`
78 MaxDBIndexes int64 `yaml:"max_db_indexes" json:"max_db_indexes"`
74 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
79 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
80 +}
81 +
82 +type FunctionsConfig struct {
83 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
84 +}
85 +
86 +type TopQueriesConfig struct {
87 + Disabled bool `yaml:"disabled" json:"disabled"`
88 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
89 + Limit int `yaml:"limit,omitempty" json:"limit"`
90 +}
91 +
92 +func (c Config) topQueriesTimeout() time.Duration {
93 + if c.Functions.TopQueries.Timeout == 0 {
94 + return c.Timeout.Duration()
95 + }
96 + return c.Functions.TopQueries.Timeout.Duration()
97 +}
98 +
99 +func (c Config) topQueriesLimit() int {
100 + if c.Functions.TopQueries.Limit <= 0 {
101 + return 500
102 + }
103 + return c.Functions.TopQueries.Limit
104 }
105
106 type (
src/go/plugin/go.d/collector/postgres/config_schema.json
+46 -7
@@ -99,13 +99,39 @@
99 10
100 ]
101 },
102 - "top_queries_limit": {
103 - "title": "Top Queries Limit",
104 - "description": "Maximum number of queries to return in the top-queries function response.",
105 - "type": "integer",
106 - "minimum": 1,
107 - "maximum": 5000,
108 - "default": 500
102 + "functions": {
103 + "title": "Functions",
104 + "description": "Configuration for Netdata functions exposed by this collector.",
105 + "type": "object",
106 + "properties": {
107 + "top_queries": {
108 + "title": "Top Queries",
109 + "description": "Configuration for the top-queries function.",
110 + "type": "object",
111 + "properties": {
112 + "disabled": {
113 + "title": "Disabled",
114 + "description": "Disable the top-queries function.",
115 + "type": "boolean",
116 + "default": false
117 + },
118 + "timeout": {
119 + "title": "Timeout",
120 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
121 + "type": "number",
122 + "minimum": 0
123 + },
124 + "limit": {
125 + "title": "Limit",
126 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
127 + "type": "integer",
128 + "minimum": 0,
129 + "maximum": 5000,
130 + "default": 500
131 + }
132 + }
133 + }
134 + }
135 }
136 },
137 "required": [
@@ -143,6 +169,12 @@
169 "transaction_time_histogram",
170 "query_time_histogram"
171 ]
172 + },
173 + {
174 + "title": "Functions",
175 + "fields": [
176 + "functions"
177 + ]
178 }
179 ]
180 },
@@ -160,6 +192,13 @@
192 },
193 "query_time_histogram": {
194 "ui:listFlavour": "list"
195 + },
196 + "functions": {
197 + "top_queries": {
198 + "disabled": {
199 + "ui:help": "WARNING: Query text may contain unmasked sensitive literals (PII). Requires pg_stat_statements extension."
200 + }
201 + }
202 }
203 }
204 }
src/go/plugin/go.d/collector/postgres/func_top_queries.go
+10 -5
@@ -175,6 +175,9 @@ func newFuncTopQueries(r *funcRouter) *funcTopQueries {
175
176 // MethodParams implements funcapi.MethodHandler.
177 func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
178 + if f.router.collector.Functions.TopQueries.Disabled {
179 + return nil, fmt.Errorf("top-queries function disabled in configuration")
180 + }
181 if f.router.collector.db == nil {
182 return nil, fmt.Errorf("collector is still initializing")
183 }
@@ -183,10 +186,15 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
186
187 // Handle implements funcapi.MethodHandler.
188 func (f *funcTopQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
189 + if f.router.collector.Functions.TopQueries.Disabled {
190 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
191 + }
192 if f.router.collector.db == nil {
193 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
194 }
189 - return f.collectTopQueries(ctx, params.Column(paramSort))
195 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
196 + defer cancel()
197 + return f.collectTopQueries(queryCtx, params.Column(paramSort))
198 }
199
200 // buildPgSortOptions builds sort options from pgAllColumns.
@@ -237,10 +245,7 @@ func (f *funcTopQueries) collectTopQueries(ctx context.Context, sortColumn strin
245 actualSortCol := f.mapAndValidateSortColumn(sortColumn, availableCols)
246
247 // Get query limit (default 500)
240 - limit := c.TopQueriesLimit
241 - if limit <= 0 {
242 - limit = 500
243 - }
248 + limit := c.topQueriesLimit()
249
250 // Build and execute query
251 query := f.buildDynamicSQL(queryCols, actualSortCol, limit)
src/go/plugin/go.d/collector/postgres/metadata.yaml
+16
@@ -120,6 +120,22 @@ modules:
120 required: false
121 group: Limits
122
123 + - name: functions.top_queries.disabled
124 + description: Disable the [top-queries](#top-queries) function.
125 + default_value: false
126 + required: false
127 + group: Functions
128 + - name: functions.top_queries.timeout
129 + description: Query timeout (seconds). Uses collector timeout if not set.
130 + default_value: ""
131 + required: false
132 + group: Functions
133 + - name: functions.top_queries.limit
134 + description: Maximum number of queries to return.
135 + default_value: 500
136 + required: false
137 + group: Functions
138 +
139 - name: vnode
140 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
141 default_value: ""
src/go/plugin/go.d/collector/postgres/testdata/config.json
+8 -1
@@ -12,5 +12,12 @@
12 123.123
13 ],
14 "max_db_tables": 123,
15 - "max_db_indexes": 123
15 + "max_db_indexes": 123,
16 + "functions": {
17 + "top_queries": {
18 + "disabled": true,
19 + "timeout": 123.123,
20 + "limit": 123
21 + }
22 + }
23 }
src/go/plugin/go.d/collector/postgres/testdata/config.yaml
+5
@@ -10,3 +10,8 @@ query_time_histogram:
10 - 123.123
11 max_db_tables: 123
12 max_db_indexes: 123
13 +functions:
14 + top_queries:
15 + disabled: yes
16 + timeout: 123.123
17 + limit: 123
src/go/plugin/go.d/collector/proxysql/collector.go
+30 -1
@@ -34,6 +34,11 @@ func New() *Collector {
34 Config: Config{
35 DSN: "stats:stats@tcp(127.0.0.1:6032)/",
36 Timeout: confopt.Duration(time.Second),
37 + Functions: FunctionsConfig{
38 + TopQueries: TopQueriesConfig{
39 + Limit: 500,
40 + },
41 + },
42 },
43
44 charts: baseCharts.Copy(),
@@ -57,7 +62,31 @@ type Config struct {
62 AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
63 DSN string `yaml:"dsn" json:"dsn"`
64 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
60 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
65 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
66 +}
67 +
68 +type FunctionsConfig struct {
69 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
70 +}
71 +
72 +type TopQueriesConfig struct {
73 + Disabled bool `yaml:"disabled" json:"disabled"`
74 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
75 + Limit int `yaml:"limit,omitempty" json:"limit"`
76 +}
77 +
78 +func (c Config) topQueriesTimeout() time.Duration {
79 + if c.Functions.TopQueries.Timeout == 0 {
80 + return c.Timeout.Duration()
81 + }
82 + return c.Functions.TopQueries.Timeout.Duration()
83 +}
84 +
85 +func (c Config) topQueriesLimit() int {
86 + if c.Functions.TopQueries.Limit <= 0 {
87 + return 500
88 + }
89 + return c.Functions.TopQueries.Limit
90 }
91
92 type Collector struct {
src/go/plugin/go.d/collector/proxysql/config_schema.json
+54 -7
@@ -31,13 +31,39 @@
31 "minimum": 0.5,
32 "default": 1
33 },
34 - "top_queries_limit": {
35 - "title": "Top Queries Limit",
36 - "description": "Maximum number of queries to return in the top-queries function response.",
37 - "type": "integer",
38 - "minimum": 1,
39 - "maximum": 5000,
40 - "default": 500
34 + "functions": {
35 + "title": "Functions",
36 + "description": "Configuration for Netdata functions exposed by this collector.",
37 + "type": "object",
38 + "properties": {
39 + "top_queries": {
40 + "title": "Top Queries",
41 + "description": "Configuration for the top-queries function.",
42 + "type": "object",
43 + "properties": {
44 + "disabled": {
45 + "title": "Disabled",
46 + "description": "Disable the top-queries function.",
47 + "type": "boolean",
48 + "default": false
49 + },
50 + "timeout": {
51 + "title": "Timeout",
52 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
53 + "type": "number",
54 + "minimum": 0
55 + },
56 + "limit": {
57 + "title": "Limit",
58 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
59 + "type": "integer",
60 + "minimum": 0,
61 + "maximum": 5000,
62 + "default": 500
63 + }
64 + }
65 + }
66 + }
67 },
68 "vnode": {
69 "title": "Vnode",
@@ -64,6 +90,27 @@
90 },
91 "timeout": {
92 "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
93 + },
94 + "ui:flavour": "tabs",
95 + "ui:options": {
96 + "tabs": [
97 + {
98 + "title": "Base",
99 + "fields": [
100 + "update_every",
101 + "autodetection_retry",
102 + "dsn",
103 + "timeout",
104 + "vnode"
105 + ]
106 + },
107 + {
108 + "title": "Functions",
109 + "fields": [
110 + "functions"
111 + ]
112 + }
113 + ]
114 }
115 }
116 }
src/go/plugin/go.d/collector/proxysql/functions.go
+10 -5
@@ -101,6 +101,9 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
101 }
102
103 c := f.router.collector
104 + if c.Functions.TopQueries.Disabled {
105 + return nil, fmt.Errorf("top-queries function disabled in configuration")
106 + }
107 if c.db == nil {
108 if err := c.openConnection(); err != nil {
109 return nil, err
@@ -117,13 +120,18 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
120 }
121
122 c := f.router.collector
123 + if c.Functions.TopQueries.Disabled {
124 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
125 + }
126 if c.db == nil {
127 if err := c.openConnection(); err != nil {
128 return funcapi.UnavailableResponse(fmt.Sprintf("failed to open connection: %v", err))
129 }
130 }
131
126 - return c.collectTopQueries(ctx, params.Column(paramSort))
132 + queryCtx, cancel := context.WithTimeout(ctx, c.topQueriesTimeout())
133 + defer cancel()
134 + return c.collectTopQueries(queryCtx, params.Column(paramSort))
135 }
136
137 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
@@ -305,10 +313,7 @@ func (c *Collector) collectTopQueries(ctx context.Context, sortColumn string) *f
313 cs := proxysqlColumnSet(cols)
314 sortColumn = c.mapAndValidateProxySQLSortColumn(sortColumn, cs)
315
308 - limit := c.TopQueriesLimit
309 - if limit <= 0 {
310 - limit = 500
311 - }
316 + limit := c.topQueriesLimit()
317
318 query := c.buildProxySQLDynamicSQL(cols, sortColumn, limit)
319 rows, err := c.db.QueryContext(ctx, query)
src/go/plugin/go.d/collector/proxysql/metadata.yaml
+16
@@ -73,6 +73,22 @@ modules:
73 required: false
74 group: Target
75
76 + - name: functions.top_queries.disabled
77 + description: Disable the [top-queries](#top-queries) function.
78 + default_value: false
79 + required: false
80 + group: Functions
81 + - name: functions.top_queries.timeout
82 + description: Query timeout (seconds). Uses collector timeout if not set.
83 + default_value: ""
84 + required: false
85 + group: Functions
86 + - name: functions.top_queries.limit
87 + description: Maximum number of queries to return.
88 + default_value: 500
89 + required: false
90 + group: Functions
91 +
92 - name: vnode
93 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
94 default_value: ""
src/go/plugin/go.d/collector/proxysql/testdata/config.json
+8 -1
@@ -3,5 +3,12 @@
3 "update_every": 123,
4 "autodetection_retry": 123,
5 "dsn": "ok",
6 - "timeout": 123.123
6 + "timeout": 123.123,
7 + "functions": {
8 + "top_queries": {
9 + "disabled": true,
10 + "timeout": 123.123,
11 + "limit": 123
12 + }
13 + }
14 }
src/go/plugin/go.d/collector/proxysql/testdata/config.yaml
+5
@@ -3,3 +3,8 @@ update_every: 123
3 autodetection_retry: 123
4 dsn: "ok"
5 timeout: 123.123
6 +functions:
7 + top_queries:
8 + disabled: true
9 + timeout: 123.123
10 + limit: 123
src/go/plugin/go.d/collector/redis/collector.go
+31 -2
@@ -44,6 +44,11 @@ func New() *Collector {
44 Address: "redis://@localhost:6379",
45 Timeout: confopt.Duration(time.Second),
46 PingSamples: 5,
47 + Functions: FunctionsConfig{
48 + TopQueries: TopQueriesConfig{
49 + Limit: 500,
50 + },
51 + },
52 },
53
54 addAOFChartsOnce: &sync.Once{},
@@ -65,8 +70,32 @@ type Config struct {
70 Username string `yaml:"username,omitempty" json:"username"`
71 Password string `yaml:"password,omitempty" json:"password"`
72 tlscfg.TLSConfig `yaml:",inline" json:""`
68 - PingSamples int `yaml:"ping_samples" json:"ping_samples"`
69 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
73 + PingSamples int `yaml:"ping_samples" json:"ping_samples"`
74 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
75 +}
76 +
77 +type FunctionsConfig struct {
78 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
79 +}
80 +
81 +type TopQueriesConfig struct {
82 + Disabled bool `yaml:"disabled" json:"disabled"`
83 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
84 + Limit int `yaml:"limit,omitempty" json:"limit"`
85 +}
86 +
87 +func (c Config) topQueriesTimeout() time.Duration {
88 + if c.Functions.TopQueries.Timeout == 0 {
89 + return c.Timeout.Duration()
90 + }
91 + return c.Functions.TopQueries.Timeout.Duration()
92 +}
93 +
94 +func (c Config) topQueriesLimit() int {
95 + if c.Functions.TopQueries.Limit <= 0 {
96 + return 500
97 + }
98 + return c.Functions.TopQueries.Limit
99 }
100
101 type (
src/go/plugin/go.d/collector/redis/config_schema.json
+38 -9
@@ -38,14 +38,6 @@
38 "minimum": 1,
39 "default": 5
40 },
41 - "top_queries_limit": {
42 - "title": "Top Queries Limit",
43 - "description": "Maximum number of queries to return in the top-queries function response.",
44 - "type": "integer",
45 - "minimum": 1,
46 - "maximum": 5000,
47 - "default": 500
48 - },
41 "vnode": {
42 "title": "Vnode",
43 "description": "Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).",
@@ -82,6 +74,38 @@
74 "title": "TLS key",
75 "description": "The path to the client key file for TLS authentication.",
76 "type": "string"
77 + },
78 + "functions": {
79 + "title": "Functions",
80 + "type": "object",
81 + "properties": {
82 + "top_queries": {
83 + "title": "Top Queries",
84 + "type": "object",
85 + "properties": {
86 + "disabled": {
87 + "title": "Disabled",
88 + "description": "Disable the top-queries function.",
89 + "type": "boolean",
90 + "default": false
91 + },
92 + "timeout": {
93 + "title": "Timeout",
94 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
95 + "type": "number",
96 + "minimum": 0
97 + },
98 + "limit": {
99 + "title": "Limit",
100 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
101 + "type": "integer",
102 + "minimum": 0,
103 + "maximum": 5000,
104 + "default": 500
105 + }
106 + }
107 + }
108 + }
109 }
110 },
111 "required": [
@@ -122,7 +146,6 @@
146 "address",
147 "timeout",
148 "ping_samples",
125 - "top_queries_limit",
149 "vnode"
150 ]
151 },
@@ -141,6 +164,12 @@
164 "tls_cert",
165 "tls_key"
166 ]
167 + },
168 + {
169 + "title": "Functions",
170 + "fields": [
171 + "functions"
172 + ]
173 }
174 ]
175 }
src/go/plugin/go.d/collector/redis/func_top_queries.go
+10 -5
@@ -76,6 +76,9 @@ func (f *funcTopQueries) MethodParams(_ context.Context, method string) ([]funca
76 if method != topQueriesMethodID {
77 return nil, fmt.Errorf("unknown method: %s", method)
78 }
79 + if f.router.collector.Functions.TopQueries.Disabled {
80 + return nil, fmt.Errorf("top-queries function disabled in configuration")
81 + }
82 return []funcapi.ParamConfig{funcapi.BuildSortParam(redisAllColumns)}, nil
83 }
84
@@ -84,12 +87,17 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
87 if method != topQueriesMethodID {
88 return funcapi.NotFoundResponse(method)
89 }
90 + if f.router.collector.Functions.TopQueries.Disabled {
91 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
92 + }
93
94 if f.router.collector.rdb == nil {
95 return funcapi.UnavailableResponse("collector is still initializing, please retry in a few seconds")
96 }
97
92 - return f.collectTopQueries(ctx, params.Column("__sort"))
98 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
99 + defer cancel()
100 + return f.collectTopQueries(queryCtx, params.Column("__sort"))
101 }
102
103 // Cleanup implements funcapi.MethodHandler.
@@ -98,10 +106,7 @@ func (f *funcTopQueries) Cleanup(ctx context.Context) {}
106 func (f *funcTopQueries) collectTopQueries(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
107 c := f.router.collector
108
101 - limit := c.TopQueriesLimit
102 - if limit <= 0 {
103 - limit = 500
104 - }
109 + limit := c.topQueriesLimit()
110
111 entries, err := c.rdb.SlowLogGet(ctx, -1).Result()
112 if err != nil {
src/go/plugin/go.d/collector/redis/metadata.yaml
+16
@@ -124,6 +124,22 @@ modules:
124 required: false
125 group: TLS
126
127 + - name: functions.top_queries.disabled
128 + description: Disable the [top-queries](#top-queries) function.
129 + default_value: false
130 + required: false
131 + group: Functions
132 + - name: functions.top_queries.timeout
133 + description: Query timeout (seconds). Uses collector timeout if not set.
134 + default_value: ""
135 + required: false
136 + group: Functions
137 + - name: functions.top_queries.limit
138 + description: Maximum number of queries to return.
139 + default_value: 500
140 + required: false
141 + group: Functions
142 +
143 - name: vnode
144 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
145 default_value: ""
src/go/plugin/go.d/collector/redis/testdata/config.json
+8 -1
@@ -10,5 +10,12 @@
10 "tls_ca": "ok",
11 "tls_cert": "ok",
12 "tls_key": "ok",
13 - "tls_skip_verify": true
13 + "tls_skip_verify": true,
14 + "functions": {
15 + "top_queries": {
16 + "disabled": true,
17 + "timeout": 123.123,
18 + "limit": 123
19 + }
20 + }
21 }
src/go/plugin/go.d/collector/redis/testdata/config.yaml
+5
@@ -10,3 +10,8 @@ tls_ca: "ok"
10 tls_cert: "ok"
11 tls_key: "ok"
12 tls_skip_verify: yes
13 +functions:
14 + top_queries:
15 + disabled: true
16 + timeout: 123.123
17 + limit: 123
src/go/plugin/go.d/collector/rethinkdb/collector.go
+30 -1
@@ -30,6 +30,11 @@ func New() *Collector {
30 Config: Config{
31 Address: "127.0.0.1:28015",
32 Timeout: confopt.Duration(time.Second * 1),
33 + Functions: FunctionsConfig{
34 + RunningQueries: RunningQueriesConfig{
35 + Limit: 500,
36 + },
37 + },
38 },
39
40 charts: clusterCharts.Copy(),
@@ -50,7 +55,31 @@ type Config struct {
55 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
56 Username string `yaml:"username,omitempty" json:"username"`
57 Password string `yaml:"password,omitempty" json:"password"`
53 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
58 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
59 +}
60 +
61 +type FunctionsConfig struct {
62 + RunningQueries RunningQueriesConfig `yaml:"running_queries,omitempty" json:"running_queries"`
63 +}
64 +
65 +type RunningQueriesConfig struct {
66 + Disabled bool `yaml:"disabled" json:"disabled"`
67 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
68 + Limit int `yaml:"limit,omitempty" json:"limit"`
69 +}
70 +
71 +func (c Config) runningQueriesTimeout() time.Duration {
72 + if c.Functions.RunningQueries.Timeout == 0 {
73 + return c.Timeout.Duration()
74 + }
75 + return c.Functions.RunningQueries.Timeout.Duration()
76 +}
77 +
78 +func (c Config) runningQueriesLimit() int {
79 + if c.Functions.RunningQueries.Limit <= 0 {
80 + return 500
81 + }
82 + return c.Functions.RunningQueries.Limit
83 }
84
85 type Collector struct {
src/go/plugin/go.d/collector/rethinkdb/config_schema.json
+38 -8
@@ -31,14 +31,6 @@
31 "minimum": 0.5,
32 "default": 1
33 },
34 - "top_queries_limit": {
35 - "title": "Top Queries Limit",
36 - "description": "Maximum number of queries to return in the running-queries function response.",
37 - "type": "integer",
38 - "minimum": 1,
39 - "maximum": 5000,
40 - "default": 500
41 - },
34 "vnode": {
35 "title": "Vnode",
36 "description": "Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).",
@@ -55,6 +47,38 @@
47 "description": "The password for basic authentication.",
48 "type": "string",
49 "sensitive": true
50 + },
51 + "functions": {
52 + "title": "Functions",
53 + "type": "object",
54 + "properties": {
55 + "running_queries": {
56 + "title": "Running Queries",
57 + "type": "object",
58 + "properties": {
59 + "disabled": {
60 + "title": "Disabled",
61 + "description": "Disable the running-queries function.",
62 + "type": "boolean",
63 + "default": false
64 + },
65 + "timeout": {
66 + "title": "Timeout",
67 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
68 + "type": "number",
69 + "minimum": 0
70 + },
71 + "limit": {
72 + "title": "Limit",
73 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
74 + "type": "integer",
75 + "minimum": 0,
76 + "maximum": 5000,
77 + "default": 500
78 + }
79 + }
80 + }
81 + }
82 }
83 },
84 "required": [
@@ -99,6 +123,12 @@
123 "username",
124 "password"
125 ]
126 + },
127 + {
128 + "title": "Functions",
129 + "fields": [
130 + "functions"
131 + ]
132 }
133 ]
134 }
src/go/plugin/go.d/collector/rethinkdb/func_running_queries.go
+14 -7
@@ -47,6 +47,9 @@ func (f *funcRunningQueries) MethodParams(_ context.Context, method string) ([]f
47 if method != runningQueriesMethodID {
48 return nil, fmt.Errorf("unknown method: %s", method)
49 }
50 + if f.router.collector.Functions.RunningQueries.Disabled {
51 + return nil, fmt.Errorf("running-queries function disabled in configuration")
52 + }
53 return []funcapi.ParamConfig{funcapi.BuildSortParam(rethinkRunningColumns)}, nil
54 }
55
@@ -55,6 +58,9 @@ func (f *funcRunningQueries) Handle(ctx context.Context, method string, params f
58 if method != runningQueriesMethodID {
59 return funcapi.NotFoundResponse(method)
60 }
61 + if f.router.collector.Functions.RunningQueries.Disabled {
62 + return funcapi.UnavailableResponse("running-queries function has been disabled in configuration")
63 + }
64
65 return f.collectRunningQueries(ctx, params.Column("__sort"))
66 }
@@ -62,18 +68,19 @@ func (f *funcRunningQueries) Handle(ctx context.Context, method string, params f
68 func (f *funcRunningQueries) collectRunningQueries(ctx context.Context, sortColumn string) *funcapi.FunctionResponse {
69 c := f.router.collector
70
65 - limit := c.TopQueriesLimit
66 - if limit <= 0 {
67 - limit = 500
68 - }
71 + limit := c.runningQueriesLimit()
72 + timeout := c.runningQueriesTimeout()
73 +
74 + queryCtx, cancel := context.WithTimeout(ctx, timeout)
75 + defer cancel()
76
70 - if ctx.Err() == context.DeadlineExceeded {
77 + if queryCtx.Err() == context.DeadlineExceeded {
78 return &funcapi.FunctionResponse{Status: 504, Message: "query timed out"}
79 }
80
74 - rows, err := c.rdb.jobs(ctx)
81 + rows, err := c.rdb.jobs(queryCtx)
82 if err != nil {
76 - if ctx.Err() == context.DeadlineExceeded {
83 + if queryCtx.Err() == context.DeadlineExceeded {
84 return &funcapi.FunctionResponse{Status: 504, Message: "query timed out"}
85 }
86 return &funcapi.FunctionResponse{Status: 500, Message: fmt.Sprintf("jobs query failed: %v", err)}
src/go/plugin/go.d/collector/rethinkdb/metadata.yaml
+16 -5
@@ -76,11 +76,6 @@ modules:
76 default_value: 1
77 required: false
78 group: Target
79 - - name: top_queries_limit
80 - description: Maximum number of rows returned by the `running-queries` function.
81 - default_value: 500
82 - required: false
83 - group: Limits
79
80 - name: username
81 description: Username for authentication.
@@ -93,6 +88,22 @@ modules:
88 required: false
89 group: Auth
90
91 + - name: functions.running_queries.disabled
92 + description: Disable the [running-queries](#running-queries) function.
93 + default_value: false
94 + required: false
95 + group: Functions
96 + - name: functions.running_queries.timeout
97 + description: Timeout for the running-queries function query (seconds). If not set, uses the collector's timeout.
98 + default_value: (collector timeout)
99 + required: false
100 + group: Functions
101 + - name: functions.running_queries.limit
102 + description: Maximum number of rows returned by the running-queries function.
103 + default_value: 500
104 + required: false
105 + group: Functions
106 +
107 - name: vnode
108 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
109 default_value: ""
src/go/plugin/go.d/collector/rethinkdb/testdata/config.json
+8 -1
@@ -5,5 +5,12 @@
5 "address": "ok",
6 "timeout": 123.123,
7 "username": "ok",
8 - "password": "ok"
8 + "password": "ok",
9 + "functions": {
10 + "running_queries": {
11 + "disabled": true,
12 + "timeout": 123.123,
13 + "limit": 123
14 + }
15 + }
16 }
src/go/plugin/go.d/collector/rethinkdb/testdata/config.yaml
+5
@@ -5,3 +5,8 @@ address: "ok"
5 timeout: 123.123
6 username: "ok"
7 password: "ok"
8 +functions:
9 + running_queries:
10 + disabled: true
11 + timeout: 123.123
12 + limit: 123
src/go/plugin/go.d/collector/yugabytedb/collector.go
+58 -7
@@ -43,7 +43,14 @@ func New() *Collector {
43 Timeout: confopt.Duration(time.Second),
44 },
45 },
46 - SQLTimeout: confopt.Duration(time.Second),
46 + Functions: FunctionsConfig{
47 + TopQueries: TopQueriesConfig{
48 + Limit: 500,
49 + },
50 + RunningQueries: RunningQueriesConfig{
51 + Limit: 500,
52 + },
53 + },
54 },
55 charts: &module.Charts{},
56
@@ -52,15 +59,59 @@ func New() *Collector {
59 }
60
61 type Config struct {
55 - Vnode string `yaml:"vnode,omitempty" json:"vnode"`
56 - UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
57 - AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
58 - DSN string `yaml:"dsn,omitempty" json:"dsn,omitempty"`
59 - SQLTimeout confopt.Duration `yaml:"sql_timeout,omitempty" json:"sql_timeout,omitempty"`
60 - TopQueriesLimit int `yaml:"top_queries_limit,omitempty" json:"top_queries_limit,omitempty"`
62 + Vnode string `yaml:"vnode,omitempty" json:"vnode"`
63 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
64 + AutoDetectionRetry int `yaml:"autodetection_retry,omitempty" json:"autodetection_retry"`
65 + Functions FunctionsConfig `yaml:"functions,omitempty" json:"functions"`
66 web.HTTPConfig `yaml:",inline" json:""`
67 }
68
69 +type FunctionsConfig struct {
70 + DSN string `yaml:"dsn,omitempty" json:"dsn,omitempty"`
71 + TopQueries TopQueriesConfig `yaml:"top_queries,omitempty" json:"top_queries"`
72 + RunningQueries RunningQueriesConfig `yaml:"running_queries,omitempty" json:"running_queries"`
73 +}
74 +
75 +type TopQueriesConfig struct {
76 + Disabled bool `yaml:"disabled" json:"disabled"`
77 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
78 + Limit int `yaml:"limit,omitempty" json:"limit"`
79 +}
80 +
81 +type RunningQueriesConfig struct {
82 + Disabled bool `yaml:"disabled" json:"disabled"`
83 + Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
84 + Limit int `yaml:"limit,omitempty" json:"limit"`
85 +}
86 +
87 +func (c Config) topQueriesTimeout() time.Duration {
88 + if c.Functions.TopQueries.Timeout == 0 {
89 + return c.Timeout.Duration()
90 + }
91 + return c.Functions.TopQueries.Timeout.Duration()
92 +}
93 +
94 +func (c Config) topQueriesLimit() int {
95 + if c.Functions.TopQueries.Limit <= 0 {
96 + return 500
97 + }
98 + return c.Functions.TopQueries.Limit
99 +}
100 +
101 +func (c Config) runningQueriesTimeout() time.Duration {
102 + if c.Functions.RunningQueries.Timeout == 0 {
103 + return c.Timeout.Duration()
104 + }
105 + return c.Functions.RunningQueries.Timeout.Duration()
106 +}
107 +
108 +func (c Config) runningQueriesLimit() int {
109 + if c.Functions.RunningQueries.Limit <= 0 {
110 + return 500
111 + }
112 + return c.Functions.RunningQueries.Limit
113 +}
114 +
115 type Collector struct {
116 module.Base
117 Config `yaml:",inline" json:""`
src/go/plugin/go.d/collector/yugabytedb/config_schema.json
+67 -30
@@ -32,25 +32,71 @@
32 "minimum": 0.5,
33 "default": 1
34 },
35 - "dsn": {
36 - "title": "SQL DSN",
37 - "description": "YSQL Data Source Name for query functions (top-queries, running-queries).",
38 - "type": "string"
39 - },
40 - "sql_timeout": {
41 - "title": "SQL Timeout",
42 - "description": "Timeout in seconds for SQL query functions.",
43 - "type": "number",
44 - "minimum": 0.5,
45 - "default": 1
46 - },
47 - "top_queries_limit": {
48 - "title": "Top Queries Limit",
49 - "description": "Maximum number of rows returned by the top-queries and running-queries functions.",
50 - "type": "integer",
51 - "minimum": 1,
52 - "maximum": 5000,
53 - "default": 500
35 + "functions": {
36 + "title": "Functions",
37 + "description": "Configuration for Netdata functions exposed by this collector.",
38 + "type": "object",
39 + "properties": {
40 + "dsn": {
41 + "title": "SQL DSN",
42 + "description": "YSQL Data Source Name (required for query functions).",
43 + "type": "string"
44 + },
45 + "top_queries": {
46 + "title": "Top Queries",
47 + "description": "Configuration for the top-queries function.",
48 + "type": "object",
49 + "properties": {
50 + "disabled": {
51 + "title": "Disabled",
52 + "description": "Disable the top-queries function.",
53 + "type": "boolean",
54 + "default": false
55 + },
56 + "timeout": {
57 + "title": "Timeout",
58 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
59 + "type": "number",
60 + "minimum": 0
61 + },
62 + "limit": {
63 + "title": "Limit",
64 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
65 + "type": "integer",
66 + "minimum": 0,
67 + "maximum": 5000,
68 + "default": 500
69 + }
70 + }
71 + },
72 + "running_queries": {
73 + "title": "Running Queries",
74 + "description": "Configuration for the running-queries function.",
75 + "type": "object",
76 + "properties": {
77 + "disabled": {
78 + "title": "Disabled",
79 + "description": "Disable the running-queries function.",
80 + "type": "boolean",
81 + "default": false
82 + },
83 + "timeout": {
84 + "title": "Timeout",
85 + "description": "Query timeout in seconds. Set to 0 to use the collector's timeout.",
86 + "type": "number",
87 + "minimum": 0
88 + },
89 + "limit": {
90 + "title": "Limit",
91 + "description": "Maximum number of queries to return. Set to 0 to use the default (500).",
92 + "type": "integer",
93 + "minimum": 0,
94 + "maximum": 5000,
95 + "default": 500
96 + }
97 + }
98 + }
99 + }
100 },
101 "not_follow_redirects": {
102 "title": "Not follow redirects",
@@ -174,13 +220,6 @@
220 "timeout": {
221 "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
222 },
177 - "dsn": {
178 - "ui:help": "Format is `postgres://username:password@host:port/dbname?sslmode=disable`.",
179 - "ui:placeholder": "postgres://yugabyte@127.0.0.1:5433/yugabyte?sslmode=disable"
180 - },
181 - "sql_timeout": {
182 - "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
183 - },
223 "username": {
224 "ui:widget": "password"
225 },
@@ -207,11 +246,9 @@
246 ]
247 },
248 {
210 - "title": "SQL",
249 + "title": "Functions",
250 "fields": [
212 - "dsn",
213 - "sql_timeout",
214 - "top_queries_limit"
251 + "functions"
252 ]
253 },
254 {
src/go/plugin/go.d/collector/yugabytedb/func_router.go
+6 -9
@@ -19,7 +19,7 @@ var errSQLDSNNotSet = errors.New("SQL DSN is not set")
19 // funcRouter routes method calls to appropriate function handlers.
20 // Owns shared SQL connection used by all function handlers.
21 type funcRouter struct {
22 - collector *Collector // for config (DSN, SQLTimeout, TopQueriesLimit, logger)
22 + collector *Collector // for config (Functions.DSN, logger)
23
24 handlers map[string]funcapi.MethodHandler
25
@@ -79,11 +79,11 @@ func (r *funcRouter) ensureDB(ctx context.Context) error {
79 if r.db != nil {
80 return nil
81 }
82 - if r.collector.DSN == "" {
82 + if r.collector.Functions.DSN == "" {
83 return errSQLDSNNotSet
84 }
85
86 - db, err := sql.Open("pgx", r.collector.DSN)
86 + db, err := sql.Open("pgx", r.collector.Functions.DSN)
87 if err != nil {
88 return fmt.Errorf("error opening SQL connection: %w", err)
89 }
@@ -104,17 +104,14 @@ func (r *funcRouter) ensureDB(ctx context.Context) error {
104 }
105
106 func (r *funcRouter) sqlTimeout() time.Duration {
107 - if r.collector.SQLTimeout.Duration() > 0 {
108 - return r.collector.SQLTimeout.Duration()
107 + if r.collector.Timeout.Duration() > 0 {
108 + return r.collector.Timeout.Duration()
109 }
110 return time.Second
111 }
112
113 func (r *funcRouter) topQueriesLimit() int {
114 - if r.collector.TopQueriesLimit > 0 {
115 - return r.collector.TopQueriesLimit
116 - }
117 - return 500
114 + return r.collector.topQueriesLimit()
115 }
116
117 func yugabyteMethods() []funcapi.MethodConfig {
src/go/plugin/go.d/collector/yugabytedb/func_running_queries.go
+8 -2
@@ -72,12 +72,18 @@ func newFuncRunningQueries(r *funcRouter) *funcRunningQueries {
72 var _ funcapi.MethodHandler = (*funcRunningQueries)(nil)
73
74 func (f *funcRunningQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
75 + if f.router.collector.Functions.RunningQueries.Disabled {
76 + return nil, fmt.Errorf("running-queries function disabled in configuration")
77 + }
78 return []funcapi.ParamConfig{funcapi.BuildSortParam(runningQueriesColumns)}, nil
79 }
80
81 func (f *funcRunningQueries) Cleanup(ctx context.Context) {}
82
83 func (f *funcRunningQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
84 + if f.router.collector.Functions.RunningQueries.Disabled {
85 + return funcapi.UnavailableResponse("running-queries function has been disabled in configuration")
86 + }
87 if err := f.router.ensureDB(ctx); err != nil {
88 status := 503
89 if errors.Is(err, errSQLDSNNotSet) {
@@ -87,10 +93,10 @@ func (f *funcRunningQueries) Handle(ctx context.Context, method string, params f
93 }
94
95 sortColumn := f.resolveSortColumn(params.Column("__sort"))
90 - limit := f.router.topQueriesLimit()
96 + limit := f.router.collector.runningQueriesLimit()
97
98 query := f.buildSQL(sortColumn)
93 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
99 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.runningQueriesTimeout())
100 defer cancel()
101
102 rows, err := f.router.db.QueryContext(queryCtx, query, limit)
src/go/plugin/go.d/collector/yugabytedb/func_top_queries.go
+9 -3
@@ -74,6 +74,9 @@ func newFuncTopQueries(r *funcRouter) *funcTopQueries {
74 var _ funcapi.MethodHandler = (*funcTopQueries)(nil)
75
76 func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]funcapi.ParamConfig, error) {
77 + if f.router.collector.Functions.TopQueries.Disabled {
78 + return nil, fmt.Errorf("top-queries function disabled in configuration")
79 + }
80 if err := f.router.ensureDB(ctx); err != nil {
81 return nil, nil // Use static RequiredParams
82 }
@@ -87,6 +90,9 @@ func (f *funcTopQueries) MethodParams(ctx context.Context, method string) ([]fun
90 func (f *funcTopQueries) Cleanup(ctx context.Context) {}
91
92 func (f *funcTopQueries) Handle(ctx context.Context, method string, params funcapi.ResolvedParams) *funcapi.FunctionResponse {
93 + if f.router.collector.Functions.TopQueries.Disabled {
94 + return funcapi.UnavailableResponse("top-queries function has been disabled in configuration")
95 + }
96 if err := f.router.ensureDB(ctx); err != nil {
97 status := 503
98 if errors.Is(err, errSQLDSNNotSet) {
@@ -115,7 +121,7 @@ func (f *funcTopQueries) Handle(ctx context.Context, method string, params funca
121 limit := f.router.topQueriesLimit()
122
123 query := f.buildSQL(cols, sortColumn)
118 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
124 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
125 defer cancel()
126
127 rows, err := f.router.db.QueryContext(queryCtx, query, limit)
@@ -162,7 +168,7 @@ func (f *funcTopQueries) availableColumns(ctx context.Context) ([]topQueriesColu
168
169 func (f *funcTopQueries) pgStatStatementsEnabled(ctx context.Context) (bool, error) {
170 query := `SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'`
165 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
171 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
172 defer cancel()
173
174 var exists int
@@ -200,7 +206,7 @@ func (f *funcTopQueries) detectPgStatStatementsColumns(ctx context.Context) (map
206 WHERE table_name = 'pg_stat_statements'
207 AND table_schema = 'public'
208 `
203 - queryCtx, cancel := context.WithTimeout(ctx, f.router.sqlTimeout())
209 + queryCtx, cancel := context.WithTimeout(ctx, f.router.collector.topQueriesTimeout())
210 defer cancel()
211
212 rows, err := f.router.db.QueryContext(queryCtx, query)
src/go/plugin/go.d/collector/yugabytedb/metadata.yaml
+40 -16
@@ -87,21 +87,6 @@ modules:
87 default_value: 1
88 required: false
89 group: Target
90 - - name: dsn
91 - description: SQL DSN used by `top-queries` and `running-queries` functions.
92 - default_value: ""
93 - required: false
94 - group: Query Functions
95 - - name: sql_timeout
96 - description: SQL query timeout (seconds) for query functions.
97 - default_value: 1
98 - required: false
99 - group: Query Functions
100 - - name: top_queries_limit
101 - description: Maximum number of rows returned by the `top-queries` and `running-queries` functions.
102 - default_value: 500
103 - required: false
104 - group: Limits
90
91 - name: username
92 description: Username for Basic HTTP authentication.
@@ -182,6 +167,44 @@ modules:
167 required: false
168 group: Request
169
170 + - name: functions.dsn
171 + description: SQL DSN (required for query functions).
172 + default_value: ""
173 + required: false
174 + group: Functions
175 +
176 + - name: functions.top_queries.disabled
177 + description: Disable the [top-queries](#top-queries) function.
178 + default_value: false
179 + required: false
180 + group: Functions
181 + - name: functions.top_queries.timeout
182 + description: Query timeout (seconds). Uses collector timeout if not set.
183 + default_value: ""
184 + required: false
185 + group: Functions
186 + - name: functions.top_queries.limit
187 + description: Maximum number of queries to return.
188 + default_value: 500
189 + required: false
190 + group: Functions
191 +
192 + - name: functions.running_queries.disabled
193 + description: Disable the [running-queries](#running-queries) function.
194 + default_value: false
195 + required: false
196 + group: Functions
197 + - name: functions.running_queries.timeout
198 + description: Query timeout (seconds). Uses collector timeout if not set.
199 + default_value: ""
200 + required: false
201 + group: Functions
202 + - name: functions.running_queries.limit
203 + description: Maximum number of queries to return.
204 + default_value: 500
205 + required: false
206 + group: Functions
207 +
208 - name: vnode
209 description: Associates this data collection job with a [Virtual Node](https://learn.netdata.cloud/docs/netdata-agent/configuration/organize-systems-metrics-and-alerts#virtual-nodes).
210 default_value: ""
@@ -209,7 +232,8 @@ modules:
232 jobs:
233 - name: local
234 url: http://127.0.0.1:7000/prometheus-metrics
212 - dsn: postgres://yugabyte@127.0.0.1:5433/yugabyte?sslmode=disable
235 + functions:
236 + dsn: postgres://yugabyte@127.0.0.1:5433/yugabyte?sslmode=disable
237 - name: HTTP authentication
238 description: Basic HTTP authentication.
239 config: |
src/go/plugin/go.d/collector/yugabytedb/testdata/config.json
+14 -1
@@ -20,5 +20,18 @@
20 "tls_cert": "ok",
21 "tls_key": "ok",
22 "tls_skip_verify": true,
23 - "force_http2": true
23 + "force_http2": true,
24 + "functions": {
25 + "dsn": "ok",
26 + "top_queries": {
27 + "disabled": true,
28 + "timeout": 123.123,
29 + "limit": 123
30 + },
31 + "running_queries": {
32 + "disabled": true,
33 + "timeout": 123.123,
34 + "limit": 123
35 + }
36 + }
37 }
src/go/plugin/go.d/collector/yugabytedb/testdata/config.yaml
+10
@@ -19,3 +19,13 @@ tls_cert: "ok"
19 tls_key: "ok"
20 tls_skip_verify: yes
21 force_http2: yes
22 +functions:
23 + dsn: "ok"
24 + top_queries:
25 + disabled: yes
26 + timeout: 123.123
27 + limit: 123
28 + running_queries:
29 + disabled: yes
30 + timeout: 123.123
31 + limit: 123