Skip to content

Commit 1aa3613

Browse files
committed
Add max, mean, and stddev timing metrics to stat_statements collector
- Add postgres_stat_statements_max_seconds metric for maximum execution time - Add postgres_stat_statements_mean_seconds metric for average execution time - Add postgres_stat_statements_stddev_seconds metric for execution time variance - Support all PostgreSQL versions with appropriate column names - Update test cases to include new metrics Signed-off-by: Irfan Habib <[email protected]>
1 parent 94e8399 commit 1aa3613

File tree

2 files changed

+82
-22
lines changed

2 files changed

+82
-22
lines changed

collector/pg_stat_statements.go

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ var (
9494
[]string{"user", "datname", "queryid"},
9595
prometheus.Labels{},
9696
)
97+
statStatementsMaxSeconds = prometheus.NewDesc(
98+
prometheus.BuildFQName(namespace, statStatementsSubsystem, "max_seconds"),
99+
"Maximum time spent in a single execution of the statement, in seconds",
100+
[]string{"user", "datname", "queryid"},
101+
prometheus.Labels{},
102+
)
103+
statStatementsMeanSeconds = prometheus.NewDesc(
104+
prometheus.BuildFQName(namespace, statStatementsSubsystem, "mean_seconds"),
105+
"Mean time spent per execution of the statement, in seconds",
106+
[]string{"user", "datname", "queryid"},
107+
prometheus.Labels{},
108+
)
109+
statStatementsStddevSeconds = prometheus.NewDesc(
110+
prometheus.BuildFQName(namespace, statStatementsSubsystem, "stddev_seconds"),
111+
"Standard deviation of time spent in the statement, in seconds",
112+
[]string{"user", "datname", "queryid"},
113+
prometheus.Labels{},
114+
)
97115

98116
statStatementsQuery = prometheus.NewDesc(
99117
prometheus.BuildFQName(namespace, statStatementsSubsystem, "query_id"),
@@ -115,7 +133,10 @@ const (
115133
pg_stat_statements.total_time / 1000.0 as seconds_total,
116134
pg_stat_statements.rows as rows_total,
117135
pg_stat_statements.blk_read_time / 1000.0 as block_read_seconds_total,
118-
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total
136+
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total,
137+
pg_stat_statements.max_time / 1000.0 as max_seconds,
138+
pg_stat_statements.mean_time / 1000.0 as mean_seconds,
139+
pg_stat_statements.stddev_time / 1000.0 as stddev_seconds
119140
FROM pg_stat_statements
120141
JOIN pg_database
121142
ON pg_database.oid = pg_stat_statements.dbid
@@ -137,7 +158,10 @@ const (
137158
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
138159
pg_stat_statements.rows as rows_total,
139160
pg_stat_statements.blk_read_time / 1000.0 as block_read_seconds_total,
140-
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total
161+
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total,
162+
pg_stat_statements.max_exec_time / 1000.0 as max_seconds,
163+
pg_stat_statements.mean_exec_time / 1000.0 as mean_seconds,
164+
pg_stat_statements.stddev_exec_time / 1000.0 as stddev_seconds
141165
FROM pg_stat_statements
142166
JOIN pg_database
143167
ON pg_database.oid = pg_stat_statements.dbid
@@ -159,7 +183,10 @@ const (
159183
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
160184
pg_stat_statements.rows as rows_total,
161185
pg_stat_statements.shared_blk_read_time / 1000.0 as block_read_seconds_total,
162-
pg_stat_statements.shared_blk_write_time / 1000.0 as block_write_seconds_total
186+
pg_stat_statements.shared_blk_write_time / 1000.0 as block_write_seconds_total,
187+
pg_stat_statements.max_exec_time / 1000.0 as max_seconds,
188+
pg_stat_statements.mean_exec_time / 1000.0 as mean_seconds,
189+
pg_stat_statements.stddev_exec_time / 1000.0 as stddev_seconds
163190
FROM pg_stat_statements
164191
JOIN pg_database
165192
ON pg_database.oid = pg_stat_statements.dbid
@@ -201,12 +228,12 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
201228
for rows.Next() {
202229
var user, datname, queryid, statement sql.NullString
203230
var callsTotal, rowsTotal sql.NullInt64
204-
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal sql.NullFloat64
231+
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal, maxSeconds, meanSeconds, stddevSeconds sql.NullFloat64
205232
var columns []any
206233
if c.includeQueryStatement {
207-
columns = []any{&user, &datname, &queryid, &statement, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
234+
columns = []any{&user, &datname, &queryid, &statement, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal, &maxSeconds, &meanSeconds, &stddevSeconds}
208235
} else {
209-
columns = []any{&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
236+
columns = []any{&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal, &maxSeconds, &meanSeconds, &stddevSeconds}
210237
}
211238
if err := rows.Scan(columns...); err != nil {
212239
return err
@@ -280,6 +307,39 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
280307
userLabel, datnameLabel, queryidLabel,
281308
)
282309

310+
maxSecondsMetric := 0.0
311+
if maxSeconds.Valid {
312+
maxSecondsMetric = maxSeconds.Float64
313+
}
314+
ch <- prometheus.MustNewConstMetric(
315+
statStatementsMaxSeconds,
316+
prometheus.GaugeValue,
317+
maxSecondsMetric,
318+
userLabel, datnameLabel, queryidLabel,
319+
)
320+
321+
meanSecondsMetric := 0.0
322+
if meanSeconds.Valid {
323+
meanSecondsMetric = meanSeconds.Float64
324+
}
325+
ch <- prometheus.MustNewConstMetric(
326+
statStatementsMeanSeconds,
327+
prometheus.GaugeValue,
328+
meanSecondsMetric,
329+
userLabel, datnameLabel, queryidLabel,
330+
)
331+
332+
stddevSecondsMetric := 0.0
333+
if stddevSeconds.Valid {
334+
stddevSecondsMetric = stddevSeconds.Float64
335+
}
336+
ch <- prometheus.MustNewConstMetric(
337+
statStatementsStddevSeconds,
338+
prometheus.GaugeValue,
339+
stddevSecondsMetric,
340+
userLabel, datnameLabel, queryidLabel,
341+
)
342+
283343
if c.includeQueryStatement {
284344
_, ok := presentQueryIds[queryidLabel]
285345
if !ok {

collector/pg_stat_statements_test.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ func TestPGStateStatementsCollector(t *testing.T) {
3333

3434
inst := &instance{db: db, version: semver.MustParse("12.0.0")}
3535

36-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
36+
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
3737
rows := sqlmock.NewRows(columns).
38-
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2)
38+
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
3939
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery, ""))).WillReturnRows(rows)
4040

4141
ch := make(chan prometheus.Metric)
@@ -76,9 +76,9 @@ func TestPGStateStatementsCollectorWithStatement(t *testing.T) {
7676

7777
inst := &instance{db: db, version: semver.MustParse("12.0.0")}
7878

79-
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 100) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
79+
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 100) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
8080
rows := sqlmock.NewRows(columns).
81-
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2)
81+
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
8282
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery, fmt.Sprintf(pgStatStatementQuerySelect, 100)))).WillReturnRows(rows)
8383

8484
ch := make(chan prometheus.Metric)
@@ -120,9 +120,9 @@ func TestPGStateStatementsCollectorNull(t *testing.T) {
120120

121121
inst := &instance{db: db, version: semver.MustParse("13.3.7")}
122122

123-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
123+
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
124124
rows := sqlmock.NewRows(columns).
125-
AddRow(nil, nil, nil, nil, nil, nil, nil, nil)
125+
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
126126
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, ""))).WillReturnRows(rows)
127127

128128
ch := make(chan prometheus.Metric)
@@ -163,9 +163,9 @@ func TestPGStateStatementsCollectorNullWithStatement(t *testing.T) {
163163

164164
inst := &instance{db: db, version: semver.MustParse("13.3.7")}
165165

166-
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 200) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
166+
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 200) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
167167
rows := sqlmock.NewRows(columns).
168-
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil)
168+
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
169169
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, fmt.Sprintf(pgStatStatementQuerySelect, 200)))).WillReturnRows(rows)
170170

171171
ch := make(chan prometheus.Metric)
@@ -207,9 +207,9 @@ func TestPGStateStatementsCollectorNewPG(t *testing.T) {
207207

208208
inst := &instance{db: db, version: semver.MustParse("13.3.7")}
209209

210-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
210+
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
211211
rows := sqlmock.NewRows(columns).
212-
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2)
212+
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
213213
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, ""))).WillReturnRows(rows)
214214

215215
ch := make(chan prometheus.Metric)
@@ -250,9 +250,9 @@ func TestPGStateStatementsCollectorNewPGWithStatement(t *testing.T) {
250250

251251
inst := &instance{db: db, version: semver.MustParse("13.3.7")}
252252

253-
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 300) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
253+
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 300) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
254254
rows := sqlmock.NewRows(columns).
255-
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2)
255+
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
256256
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, fmt.Sprintf(pgStatStatementQuerySelect, 300)))).WillReturnRows(rows)
257257

258258
ch := make(chan prometheus.Metric)
@@ -294,9 +294,9 @@ func TestPGStateStatementsCollector_PG17(t *testing.T) {
294294

295295
inst := &instance{db: db, version: semver.MustParse("17.0.0")}
296296

297-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
297+
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
298298
rows := sqlmock.NewRows(columns).
299-
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2)
299+
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
300300
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery_PG17, ""))).WillReturnRows(rows)
301301

302302
ch := make(chan prometheus.Metric)
@@ -337,9 +337,9 @@ func TestPGStateStatementsCollector_PG17_WithStatement(t *testing.T) {
337337

338338
inst := &instance{db: db, version: semver.MustParse("17.0.0")}
339339

340-
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 300) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
340+
columns := []string{"user", "datname", "queryid", "LEFT(pg_stat_statements.query, 300) as query", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total", "max_seconds", "mean_seconds", "stddev_seconds"}
341341
rows := sqlmock.NewRows(columns).
342-
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2)
342+
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
343343
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery_PG17, fmt.Sprintf(pgStatStatementQuerySelect, 300)))).WillReturnRows(rows)
344344

345345
ch := make(chan prometheus.Metric)

0 commit comments

Comments
 (0)