Skip to content

Commit bf23739

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 bf23739

File tree

2 files changed

+94
-22
lines changed

2 files changed

+94
-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: 28 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)
@@ -97,6 +97,9 @@ func TestPGStateStatementsCollectorWithStatement(t *testing.T) {
9797
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 100},
9898
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.1},
9999
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.2},
100+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.8},
101+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.08},
102+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.05},
100103
{labels: labelMap{"queryid": "1500", "query": "select 1 from foo"}, metricType: dto.MetricType_COUNTER, value: 1},
101104
}
102105

@@ -120,9 +123,9 @@ func TestPGStateStatementsCollectorNull(t *testing.T) {
120123

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

123-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
126+
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"}
124127
rows := sqlmock.NewRows(columns).
125-
AddRow(nil, nil, nil, nil, nil, nil, nil, nil)
128+
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
126129
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, ""))).WillReturnRows(rows)
127130

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

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

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"}
169+
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"}
167170
rows := sqlmock.NewRows(columns).
168-
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil)
171+
AddRow(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
169172
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, fmt.Sprintf(pgStatStatementQuerySelect, 200)))).WillReturnRows(rows)
170173

171174
ch := make(chan prometheus.Metric)
@@ -184,6 +187,9 @@ func TestPGStateStatementsCollectorNullWithStatement(t *testing.T) {
184187
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
185188
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
186189
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
190+
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
191+
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
192+
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
187193
{labels: labelMap{"queryid": "unknown", "query": "unknown"}, metricType: dto.MetricType_COUNTER, value: 1},
188194
}
189195

@@ -207,9 +213,9 @@ func TestPGStateStatementsCollectorNewPG(t *testing.T) {
207213

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

210-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
216+
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"}
211217
rows := sqlmock.NewRows(columns).
212-
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2)
218+
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
213219
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, ""))).WillReturnRows(rows)
214220

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

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

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"}
259+
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"}
254260
rows := sqlmock.NewRows(columns).
255-
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2)
261+
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
256262
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsNewQuery, fmt.Sprintf(pgStatStatementQuerySelect, 300)))).WillReturnRows(rows)
257263

258264
ch := make(chan prometheus.Metric)
@@ -271,6 +277,9 @@ func TestPGStateStatementsCollectorNewPGWithStatement(t *testing.T) {
271277
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 100},
272278
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.1},
273279
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.2},
280+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.8},
281+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.08},
282+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.05},
274283
{labels: labelMap{"queryid": "1500", "query": "select 1 from foo"}, metricType: dto.MetricType_COUNTER, value: 1},
275284
}
276285

@@ -294,9 +303,9 @@ func TestPGStateStatementsCollector_PG17(t *testing.T) {
294303

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

297-
columns := []string{"user", "datname", "queryid", "calls_total", "seconds_total", "rows_total", "block_read_seconds_total", "block_write_seconds_total"}
306+
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"}
298307
rows := sqlmock.NewRows(columns).
299-
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2)
308+
AddRow("postgres", "postgres", 1500, 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
300309
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery_PG17, ""))).WillReturnRows(rows)
301310

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

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

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"}
349+
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"}
341350
rows := sqlmock.NewRows(columns).
342-
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2)
351+
AddRow("postgres", "postgres", 1500, "select 1 from foo", 5, 0.4, 100, 0.1, 0.2, 0.8, 0.08, 0.05)
343352
mock.ExpectQuery(sanitizeQuery(fmt.Sprintf(pgStatStatementsQuery_PG17, fmt.Sprintf(pgStatStatementQuerySelect, 300)))).WillReturnRows(rows)
344353

345354
ch := make(chan prometheus.Metric)
@@ -358,6 +367,9 @@ func TestPGStateStatementsCollector_PG17_WithStatement(t *testing.T) {
358367
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 100},
359368
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.1},
360369
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.2},
370+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.8},
371+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.08},
372+
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.05},
361373
{labels: labelMap{"queryid": "1500", "query": "select 1 from foo"}, metricType: dto.MetricType_COUNTER, value: 1},
362374
}
363375

0 commit comments

Comments
 (0)