Skip to content

Commit a19f396

Browse files
authored
feat(postgresql): add disable_pg_stat_database and disable_pg_stat_bgwriter config options (#1430)
* fix(postgresql): add rows.Err() checks * feat(postgresql): add disable_pg_stat_database and disable_pg_stat_bgwriter config options
1 parent 52460bc commit a19f396

6 files changed

Lines changed: 388 additions & 102 deletions

File tree

conf/input.postgresql/postgresql.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,26 @@
3535
## databases are gathered. Do NOT use with the 'ignored_databases' option.
3636
# databases = ["app_production", "testing"]
3737

38+
## Whether to collect statement-level metrics.
39+
## Requires extension pg_stat_statements enabled, see https://www.postgresql.org/docs/current/pgstatstatements.html
40+
# enable_statement_metrics = false
41+
## Max number of statements to collect
42+
## applies only when enable_statement_metrics=true
43+
## 0 means no limit
44+
# statement_metrics_limit = 100
45+
3846
## Whether to use prepared statements when connecting to the database.
3947
## This should be set to false when connecting through a PgBouncer instance
4048
## with pool_mode set to transaction.
4149
#prepared_statements = true
50+
51+
## Whether to skip built-in metrics from pg_stat_database.
52+
# disable_pg_stat_database = false
53+
54+
## Whether to skip built-in metrics from pg_stat_bgwriter.
55+
## On PostgreSQL 17+, this also skips compatibility metrics collected from pg_stat_checkpointer.
56+
# disable_pg_stat_bgwriter = false
57+
4258
# [[instances.metrics]]
4359
# mesurement = "sessions"
4460
# label_fields = [ "status", "type" ]

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ require (
176176
require (
177177
cloud.google.com/go/monitoring v1.16.3
178178
github.com/AlekSi/pointer v1.2.0
179+
github.com/DATA-DOG/go-sqlmock v1.5.2
179180
github.com/IBM/sarama v1.42.1
180181
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
181182
github.com/NVIDIA/go-dcgm v0.0.0-20240118201113-3385e277e49f

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,8 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
643643
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
644644
github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=
645645
github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
646+
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
647+
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
646648
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
647649
github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ=
648650
github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
@@ -1412,6 +1414,7 @@ github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeW
14121414
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
14131415
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
14141416
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
1417+
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
14151418
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
14161419
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
14171420
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=

inputs/postgresql/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ address = ""
5050
## with pool_mode set to transaction.
5151
## 是否使用prepared statements 连接数据库
5252
# prepared_statements = true
53+
54+
## Whether to skip built-in metrics from pg_stat_database.
55+
# disable_pg_stat_database = false
56+
57+
## Whether to skip built-in metrics from pg_stat_bgwriter.
58+
## On PostgreSQL 17+, this also skips compatibility metrics collected from pg_stat_checkpointer.
59+
# disable_pg_stat_bgwriter = false
5360
```
5461
![dashboard](./postgresql.png)
5562

inputs/postgresql/postgresql.go

Lines changed: 131 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ type Instance struct {
8181
PreparedStatements bool `toml:"prepared_statements"`
8282
EnableStatementMetrics bool `toml:"enable_statement_metrics"`
8383
StatementMetricsLimit int `toml:"statement_metrics_limit"`
84+
DisablePgStatDatabase bool `toml:"disable_pg_stat_database"`
85+
DisablePgStatBgwriter bool `toml:"disable_pg_stat_bgwriter"`
8486
Metrics []MetricConfig `toml:"metrics"`
8587
TrimServerTagSpace bool `toml:"trim_server_tag_space"`
8688

@@ -94,7 +96,7 @@ type Instance struct {
9496

9597
var ignoredColumns = map[string]bool{"stats_reset": true}
9698

97-
func (p *Instance) IgnoredColumns() map[string]bool {
99+
func (ins *Instance) IgnoredColumns() map[string]bool {
98100
return ignoredColumns
99101
}
100102

@@ -142,17 +144,12 @@ func (ins *Instance) Init() error {
142144
}
143145

144146
// closes any necessary channels and connections
145-
func (p *Instance) Drop() {
147+
func (ins *Instance) Drop() {
146148
// Ignore the returned error as we cannot do anything about it anyway
147149
//nolint:errcheck,revive
148150
}
149151

150152
func (ins *Instance) Gather(slist *types.SampleList) {
151-
var (
152-
err error
153-
query string
154-
columns []string
155-
)
156153
addr, err := ins.SanitizedAddress()
157154
if err != nil {
158155
log.Println("E! can't sanitize address :", err)
@@ -171,40 +168,55 @@ func (ins *Instance) Gather(slist *types.SampleList) {
171168
}
172169
slist.PushSample(inputName, "up", 1, tags)
173170

171+
ins.gatherMetrics(slist)
172+
}
173+
174+
func (ins *Instance) gatherMetrics(slist *types.SampleList) {
175+
var (
176+
err error
177+
query string
178+
columns []string
179+
)
180+
174181
ins.db.SetMaxOpenConns(ins.MaxOpen)
175182
ins.db.SetMaxIdleConns(ins.MaxIdle)
176183
ins.db.SetConnMaxLifetime(time.Duration(ins.MaxLifetime))
177184

178-
if len(ins.Databases) == 0 && len(ins.IgnoredDatabases) == 0 {
179-
query = `SELECT * FROM pg_stat_database`
180-
} else if len(ins.IgnoredDatabases) != 0 {
181-
query = fmt.Sprintf(`SELECT * FROM pg_stat_database WHERE datname NOT IN ('%s')`,
182-
strings.Join(ins.IgnoredDatabases, "','"))
183-
} else {
184-
query = fmt.Sprintf(`SELECT * FROM pg_stat_database WHERE datname IN ('%s')`,
185-
strings.Join(ins.Databases, "','"))
186-
}
187-
188-
rows, err := ins.db.Query(query)
189-
if err != nil {
190-
log.Println("E! failed to execute Query :", err)
191-
return
192-
}
185+
if !ins.DisablePgStatDatabase {
186+
if len(ins.Databases) == 0 && len(ins.IgnoredDatabases) == 0 {
187+
query = `SELECT * FROM pg_stat_database`
188+
} else if len(ins.IgnoredDatabases) != 0 {
189+
query = fmt.Sprintf(`SELECT * FROM pg_stat_database WHERE datname NOT IN ('%s')`,
190+
strings.Join(ins.IgnoredDatabases, "','"))
191+
} else {
192+
query = fmt.Sprintf(`SELECT * FROM pg_stat_database WHERE datname IN ('%s')`,
193+
strings.Join(ins.Databases, "','"))
194+
}
193195

194-
defer rows.Close()
196+
rows, err := ins.db.Query(query)
197+
if err != nil {
198+
log.Println("E! failed to execute Query :", err)
199+
return
200+
}
195201

196-
// grab the column information from the result
197-
if columns, err = rows.Columns(); err != nil {
198-
log.Println("E! failed to grab column info:", err)
199-
return
200-
}
202+
defer rows.Close()
201203

202-
for rows.Next() {
203-
err = ins.accRow(rows, slist, "", columns, columns, nil)
204-
if err != nil {
205-
log.Println("E! failed to get row data:", err)
204+
// grab the column information from the result
205+
if columns, err = rows.Columns(); err != nil {
206+
log.Println("E! failed to grab column info:", err)
206207
return
207208
}
209+
210+
for rows.Next() {
211+
err = ins.accRow(rows, slist, "", columns, columns, nil)
212+
if err != nil {
213+
log.Println("E! failed to get row data:", err)
214+
return
215+
}
216+
}
217+
if err := rows.Err(); err != nil {
218+
log.Println("E! failed to iterate pg_stat_database rows:", err)
219+
}
208220
}
209221

210222
// Check Postgres Version
@@ -218,89 +230,100 @@ func (ins *Instance) Gather(slist *types.SampleList) {
218230
ins.Version = version
219231
}
220232

221-
if ins.Version < 170000 {
222-
query = `SELECT * FROM pg_stat_bgwriter`
223-
bgWriterRow, err := ins.db.Query(query)
224-
if err != nil {
225-
log.Println("E! failed to execute Query:", err)
226-
return
227-
}
228-
229-
defer bgWriterRow.Close()
230-
231-
// grab the column information from the result
232-
if columns, err = bgWriterRow.Columns(); err != nil {
233-
log.Println("E! failed to grab column info:", err)
234-
return
235-
}
236-
237-
for bgWriterRow.Next() {
238-
err = ins.accRow(bgWriterRow, slist, "", columns, columns, nil)
233+
if !ins.DisablePgStatBgwriter {
234+
if ins.Version < 170000 {
235+
query = `SELECT * FROM pg_stat_bgwriter`
236+
bgWriterRow, err := ins.db.Query(query)
239237
if err != nil {
240-
log.Println("E! failed to get row data:", err)
238+
log.Println("E! failed to execute Query:", err)
241239
return
242240
}
243-
}
244-
} else {
245-
// PG 17+ split pg_stat_bgwriter into pg_stat_bgwriter and pg_stat_checkpointer
246241

247-
// 1. Query pg_stat_bgwriter (remaining columns)
248-
query = `SELECT * FROM pg_stat_bgwriter`
249-
bgWriterRow, err := ins.db.Query(query)
250-
if err != nil {
251-
log.Println("E! failed to execute Query pg_stat_bgwriter:", err)
252-
return
253-
}
254-
defer bgWriterRow.Close()
242+
defer bgWriterRow.Close()
255243

256-
if columns, err = bgWriterRow.Columns(); err != nil {
257-
log.Println("E! failed to grab column info for pg_stat_bgwriter:", err)
258-
return
259-
}
244+
// grab the column information from the result
245+
if columns, err = bgWriterRow.Columns(); err != nil {
246+
log.Println("E! failed to grab column info:", err)
247+
return
248+
}
260249

261-
for bgWriterRow.Next() {
262-
err = ins.accRow(bgWriterRow, slist, "", columns, columns, nil)
250+
for bgWriterRow.Next() {
251+
err = ins.accRow(bgWriterRow, slist, "", columns, columns, nil)
252+
if err != nil {
253+
log.Println("E! failed to get row data:", err)
254+
return
255+
}
256+
}
257+
if err := bgWriterRow.Err(); err != nil {
258+
log.Println("E! failed to iterate pg_stat_bgwriter rows:", err)
259+
}
260+
} else {
261+
// PG 17+ split pg_stat_bgwriter into pg_stat_bgwriter and pg_stat_checkpointer
262+
263+
// 1. Query pg_stat_bgwriter (remaining columns)
264+
query = `SELECT * FROM pg_stat_bgwriter`
265+
bgWriterRow, err := ins.db.Query(query)
263266
if err != nil {
264-
log.Println("E! failed to get row data from pg_stat_bgwriter:", err)
267+
log.Println("E! failed to execute Query pg_stat_bgwriter:", err)
265268
return
266269
}
267-
}
270+
defer bgWriterRow.Close()
268271

269-
// 2. Query pg_stat_checkpointer (moved columns, aliased to old names for compatibility)
270-
// num_timed -> checkpoints_timed
271-
// num_requested -> checkpoints_req
272-
// write_time -> checkpoint_write_time
273-
// sync_time -> checkpoint_sync_time
274-
// buffers_written -> buffers_checkpoint
275-
query = `SELECT
276-
num_timed AS checkpoints_timed,
277-
num_requested AS checkpoints_req,
278-
write_time AS checkpoint_write_time,
279-
sync_time AS checkpoint_sync_time,
280-
buffers_written AS buffers_checkpoint,
281-
restartpoints_timed,
282-
restartpoints_req,
283-
restartpoints_done
284-
FROM pg_stat_checkpointer`
285-
286-
checkpointerRow, err := ins.db.Query(query)
287-
if err != nil {
288-
log.Println("E! failed to get row data:", err)
289-
return
290-
}
291-
defer checkpointerRow.Close()
272+
if columns, err = bgWriterRow.Columns(); err != nil {
273+
log.Println("E! failed to grab column info for pg_stat_bgwriter:", err)
274+
return
275+
}
292276

293-
if columns, err = checkpointerRow.Columns(); err != nil {
294-
log.Println("E! failed to grab column info for pg_stat_checkpointer:", err)
295-
return
296-
}
277+
for bgWriterRow.Next() {
278+
err = ins.accRow(bgWriterRow, slist, "", columns, columns, nil)
279+
if err != nil {
280+
log.Println("E! failed to get row data from pg_stat_bgwriter:", err)
281+
return
282+
}
283+
}
284+
if err := bgWriterRow.Err(); err != nil {
285+
log.Println("E! failed to iterate pg_stat_bgwriter rows:", err)
286+
}
297287

298-
for checkpointerRow.Next() {
299-
err = ins.accRow(checkpointerRow, slist, "", columns, columns, nil)
288+
// 2. Query pg_stat_checkpointer (moved columns, aliased to old names for compatibility)
289+
// num_timed -> checkpoints_timed
290+
// num_requested -> checkpoints_req
291+
// write_time -> checkpoint_write_time
292+
// sync_time -> checkpoint_sync_time
293+
// buffers_written -> buffers_checkpoint
294+
query = `SELECT
295+
num_timed AS checkpoints_timed,
296+
num_requested AS checkpoints_req,
297+
write_time AS checkpoint_write_time,
298+
sync_time AS checkpoint_sync_time,
299+
buffers_written AS buffers_checkpoint,
300+
restartpoints_timed,
301+
restartpoints_req,
302+
restartpoints_done
303+
FROM pg_stat_checkpointer`
304+
305+
checkpointerRow, err := ins.db.Query(query)
300306
if err != nil {
301-
log.Println("E! failed to get row data from pg_stat_checkpointer:", err)
307+
log.Println("E! failed to execute Query pg_stat_checkpointer:", err)
302308
return
303309
}
310+
defer checkpointerRow.Close()
311+
312+
if columns, err = checkpointerRow.Columns(); err != nil {
313+
log.Println("E! failed to grab column info for pg_stat_checkpointer:", err)
314+
return
315+
}
316+
317+
for checkpointerRow.Next() {
318+
err = ins.accRow(checkpointerRow, slist, "", columns, columns, nil)
319+
if err != nil {
320+
log.Println("E! failed to get row data from pg_stat_checkpointer:", err)
321+
return
322+
}
323+
}
324+
if err := checkpointerRow.Err(); err != nil {
325+
log.Println("E! failed to iterate pg_stat_checkpointer rows:", err)
326+
}
304327
}
305328
}
306329

@@ -396,6 +419,9 @@ func (ins *Instance) getStatementMetrics(slist *types.SampleList, version int) {
396419
return
397420
}
398421
}
422+
if err := statements.Err(); err != nil {
423+
log.Println("E! failed to iterate pg_stat_statements rows:", err)
424+
}
399425
}
400426

401427
func (ins *Instance) scrapeMetric(waitMetrics *sync.WaitGroup, slist *types.SampleList, metricConf MetricConfig, tags map[string]string) {
@@ -458,6 +484,9 @@ func (ins *Instance) scrapeMetric(waitMetrics *sync.WaitGroup, slist *types.Samp
458484
log.Println("E! no metrics found while parsing")
459485
}
460486
}
487+
if err := rows.Err(); err != nil {
488+
log.Println("E! failed to iterate custom query rows:", err)
489+
}
461490
}
462491

463492
func (ins *Instance) parseRow(row map[string]string, metricConf MetricConfig, slist *types.SampleList, tags map[string]string) error {
@@ -523,10 +552,10 @@ func (ins *Instance) accRow(row scanner, slist *types.SampleList, prefix string,
523552

524553
// deconstruct array of variables and send to Scan
525554
err := row.Scan(columnVars...)
526-
527555
if err != nil {
528556
return err
529557
}
558+
530559
if columnMap["datname"] != nil {
531560
// extract the database name from the column map
532561
if dbNameStr, ok := (*columnMap["datname"]).(string); ok {
@@ -658,4 +687,4 @@ func (ins *Instance) SanitizedAddress() (sanitizedAddress string, err error) {
658687
}
659688

660689
return sanitizedAddress, err
661-
}
690+
}

0 commit comments

Comments
 (0)