Skip to content

Commit badb2cb

Browse files
authored
Fix mapping and race condition in sql module (#15738)
Mapping was defined at the metricset level, but it should be defined at the module level. Add system test to earlier detect this kind of issues. Also add validation so driver and sql_query options cannot be empty. Fetch was reusing some maps when building the event, what was causing mixed data in events and panics on queries with lots of rows. Fixes #15736
1 parent 005f474 commit badb2cb

6 files changed

Lines changed: 86 additions & 67 deletions

File tree

metricbeat/docs/fields.asciidoc

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30466,25 +30466,12 @@ type: long
3046630466
[[exported-fields-sql]]
3046730467
== sql fields
3046830468
30469-
sql module
30469+
SQL module fetches metrics from a SQL database
3047030470
3047130471
3047230472
30473-
[float]
30474-
=== sql
30475-
30476-
`sql` fetches metrics from a SQL database
30477-
30478-
30479-
30480-
[float]
30481-
=== query
30482-
30483-
query
30484-
30485-
3048630473
30487-
*`sql.query.driver`*::
30474+
*`sql.driver`*::
3048830475
+
3048930476
--
3049030477
Driver used to execute the query.
@@ -30494,7 +30481,7 @@ type: keyword
3049430481
3049530482
--
3049630483
30497-
*`sql.query.query`*::
30484+
*`sql.query`*::
3049830485
+
3049930486
--
3050030487
Query executed to collect metrics.
@@ -30504,7 +30491,7 @@ type: keyword
3050430491
3050530492
--
3050630493
30507-
*`sql.query.metrics.numeric.*`*::
30494+
*`sql.metrics.numeric.*`*::
3050830495
+
3050930496
--
3051030497
Numeric metrics collected.
@@ -30514,7 +30501,7 @@ type: object
3051430501
3051530502
--
3051630503
30517-
*`sql.query.metrics.string.*`*::
30504+
*`sql.metrics.string.*`*::
3051830505
+
3051930506
--
3052030507
Non-numeric values collected.

x-pack/metricbeat/module/sql/_meta/fields.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,26 @@
22
title: "sql"
33
release: beta
44
description: >
5-
sql module
5+
SQL module fetches metrics from a SQL database
66
fields:
77
- name: sql
88
type: group
9-
description: >
10-
`sql` fetches metrics from a SQL database
119
fields:
10+
- name: driver
11+
type: keyword
12+
description: >
13+
Driver used to execute the query.
14+
- name: query
15+
type: keyword
16+
description: >
17+
Query executed to collect metrics.
18+
- name: metrics.numeric.*
19+
type: object
20+
object_type: double
21+
description: >
22+
Numeric metrics collected.
23+
- name: metrics.string.*
24+
type: object
25+
object_type: keyword
26+
description: >
27+
Non-numeric values collected.

x-pack/metricbeat/module/sql/fields.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1 @@
1-
- name: query
2-
type: group
3-
release: beta
4-
description: >
5-
query
6-
fields:
7-
- name: driver
8-
type: keyword
9-
description: >
10-
Driver used to execute the query.
11-
- name: query
12-
type: keyword
13-
description: >
14-
Query executed to collect metrics.
15-
- name: metrics.numeric.*
16-
type: object
17-
object_type: double
18-
description: >
19-
Numeric metrics collected.
20-
- name: metrics.string.*
21-
type: object
22-
object_type: keyword
23-
description: >
24-
Non-numeric values collected.
1+
- release: beta

x-pack/metricbeat/module/sql/query/query.go

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) {
4444
cfgwarn.Beta("The sql query metricset is beta.")
4545

4646
config := struct {
47-
Driver string `config:"driver"`
48-
Query string `config:"sql_query"`
47+
Driver string `config:"driver" validate:"nonzero,required"`
48+
Query string `config:"sql_query" validate:"nonzero,required"`
4949
}{}
5050

5151
if err := base.Module().UnpackConfig(&config); err != nil {
@@ -96,41 +96,39 @@ func (m *MetricSet) Fetch(report mb.ReporterV2) error {
9696
vals[i] = new(interface{})
9797
}
9898

99-
numericValue := common.MapStr{}
100-
101-
stringValue := common.MapStr{}
102-
10399
for rows.Next() {
104100
err = rows.Scan(vals...)
105101
if err != nil {
106102
m.Logger().Debug(errors.Wrap(err, "error trying to scan rows"))
107103
continue
108104
}
109105

110-
data := common.MapStr{
111-
"metrics": common.MapStr{},
112-
}
113-
data.Put("driver", m.Driver)
114-
data.Put("query", m.Query)
106+
numericMetrics := common.MapStr{}
107+
stringMetrics := common.MapStr{}
115108

116109
for i := 0; i < len(vals); i++ {
117-
dataMetrics := data["metrics"].(common.MapStr)
118-
119110
value := getValue(vals[i].(*interface{}))
120-
121111
num, err := strconv.ParseFloat(value, 64)
122112
if err == nil {
123-
numericValue.Put(cols[i], num)
124-
dataMetrics.Put("numeric", numericValue)
113+
numericMetrics[cols[i]] = num
125114
} else {
126-
stringValue.Put(cols[i], value)
127-
dataMetrics.Put("string", stringValue)
115+
stringMetrics[cols[i]] = value
128116
}
129117

130-
report.Event(mb.Event{
131-
RootFields: common.MapStr{"sql": data},
132-
})
133118
}
119+
120+
report.Event(mb.Event{
121+
RootFields: common.MapStr{
122+
"sql": common.MapStr{
123+
"driver": m.Driver,
124+
"query": m.Query,
125+
"metrics": common.MapStr{
126+
"numeric": numericMetrics,
127+
"string": stringMetrics,
128+
},
129+
},
130+
},
131+
})
134132
}
135133

136134
if rows.Err() != nil {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
import sys
3+
import unittest
4+
5+
sys.path.append(os.path.join(os.path.dirname(__file__), '../../tests/system'))
6+
from xpack_metricbeat import XPackTest, metricbeat
7+
8+
9+
class Test(XPackTest):
10+
11+
COMPOSE_SERVICES = ['mysql']
12+
13+
@unittest.skipUnless(metricbeat.INTEGRATION_TESTS, "integration test")
14+
def test_query(self):
15+
"""
16+
sql custom query test
17+
"""
18+
self.render_config_template(modules=[{
19+
"name": "sql",
20+
"metricsets": ["query"],
21+
"hosts": self.get_hosts(),
22+
"period": "5s",
23+
"additional_content": """
24+
driver: mysql
25+
sql_query: 'select table_schema, table_name, engine, table_rows from information_schema.tables where table_rows > 0'"""
26+
}])
27+
proc = self.start_beat(home=self.beat_path)
28+
self.wait_until(lambda: self.output_lines() > 0)
29+
proc.check_kill_and_wait()
30+
self.assert_no_logged_warnings()
31+
32+
output = self.read_output_json()
33+
self.assertGreater(len(output), 0)
34+
35+
for evt in output:
36+
self.assert_fields_are_documented(evt)
37+
self.assertIn("sql", evt.keys(), evt)
38+
self.assertIn("query", evt["sql"].keys(), evt)
39+
40+
def get_hosts(self):
41+
return ['root:test@tcp({})/'.format(self.compose_host())]

0 commit comments

Comments
 (0)