Skip to content

Add max, mean, and stddev timing metrics to stat_statements collector #1162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 66 additions & 6 deletions collector/pg_stat_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ var (
[]string{"user", "datname", "queryid"},
prometheus.Labels{},
)
statStatementsMaxSeconds = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "max_seconds"),
"Maximum time spent in a single execution of the statement, in seconds",
[]string{"user", "datname", "queryid"},
prometheus.Labels{},
)
statStatementsMeanSeconds = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "mean_seconds"),
"Mean time spent per execution of the statement, in seconds",
[]string{"user", "datname", "queryid"},
prometheus.Labels{},
)
statStatementsStddevSeconds = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "stddev_seconds"),
"Standard deviation of time spent in the statement, in seconds",
[]string{"user", "datname", "queryid"},
prometheus.Labels{},
)

statStatementsQuery = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "query_id"),
Expand All @@ -115,7 +133,10 @@ const (
pg_stat_statements.total_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
pg_stat_statements.blk_read_time / 1000.0 as block_read_seconds_total,
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total,
pg_stat_statements.max_time / 1000.0 as max_seconds,
pg_stat_statements.mean_time / 1000.0 as mean_seconds,
pg_stat_statements.stddev_time / 1000.0 as stddev_seconds
FROM pg_stat_statements
JOIN pg_database
ON pg_database.oid = pg_stat_statements.dbid
Expand All @@ -137,7 +158,10 @@ const (
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
pg_stat_statements.blk_read_time / 1000.0 as block_read_seconds_total,
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total
pg_stat_statements.blk_write_time / 1000.0 as block_write_seconds_total,
pg_stat_statements.max_exec_time / 1000.0 as max_seconds,
pg_stat_statements.mean_exec_time / 1000.0 as mean_seconds,
pg_stat_statements.stddev_exec_time / 1000.0 as stddev_seconds
FROM pg_stat_statements
JOIN pg_database
ON pg_database.oid = pg_stat_statements.dbid
Expand All @@ -159,7 +183,10 @@ const (
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
pg_stat_statements.shared_blk_read_time / 1000.0 as block_read_seconds_total,
pg_stat_statements.shared_blk_write_time / 1000.0 as block_write_seconds_total
pg_stat_statements.shared_blk_write_time / 1000.0 as block_write_seconds_total,
pg_stat_statements.max_exec_time / 1000.0 as max_seconds,
pg_stat_statements.mean_exec_time / 1000.0 as mean_seconds,
pg_stat_statements.stddev_exec_time / 1000.0 as stddev_seconds
FROM pg_stat_statements
JOIN pg_database
ON pg_database.oid = pg_stat_statements.dbid
Expand Down Expand Up @@ -201,12 +228,12 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
for rows.Next() {
var user, datname, queryid, statement sql.NullString
var callsTotal, rowsTotal sql.NullInt64
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal sql.NullFloat64
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal, maxSeconds, meanSeconds, stddevSeconds sql.NullFloat64
var columns []any
if c.includeQueryStatement {
columns = []any{&user, &datname, &queryid, &statement, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
columns = []any{&user, &datname, &queryid, &statement, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal, &maxSeconds, &meanSeconds, &stddevSeconds}
} else {
columns = []any{&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
columns = []any{&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal, &maxSeconds, &meanSeconds, &stddevSeconds}
}
if err := rows.Scan(columns...); err != nil {
return err
Expand Down Expand Up @@ -280,6 +307,39 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
userLabel, datnameLabel, queryidLabel,
)

maxSecondsMetric := 0.0
if maxSeconds.Valid {
maxSecondsMetric = maxSeconds.Float64
}
ch <- prometheus.MustNewConstMetric(
statStatementsMaxSeconds,
prometheus.GaugeValue,
maxSecondsMetric,
userLabel, datnameLabel, queryidLabel,
)

meanSecondsMetric := 0.0
if meanSeconds.Valid {
meanSecondsMetric = meanSeconds.Float64
}
ch <- prometheus.MustNewConstMetric(
statStatementsMeanSeconds,
prometheus.GaugeValue,
meanSecondsMetric,
userLabel, datnameLabel, queryidLabel,
)

stddevSecondsMetric := 0.0
if stddevSeconds.Valid {
stddevSecondsMetric = stddevSeconds.Float64
}
ch <- prometheus.MustNewConstMetric(
statStatementsStddevSeconds,
prometheus.GaugeValue,
stddevSecondsMetric,
userLabel, datnameLabel, queryidLabel,
)

if c.includeQueryStatement {
_, ok := presentQueryIds[queryidLabel]
if !ok {
Expand Down
44 changes: 28 additions & 16 deletions collector/pg_stat_statements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ func TestPGStateStatementsCollector(t *testing.T) {

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

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

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

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

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

ch := make(chan prometheus.Metric)
Expand All @@ -97,6 +97,9 @@ func TestPGStateStatementsCollectorWithStatement(t *testing.T) {
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 100},
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.1},
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_COUNTER, value: 0.2},
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.8},
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.08},
{labels: labelMap{"user": "postgres", "datname": "postgres", "queryid": "1500"}, metricType: dto.MetricType_GAUGE, value: 0.05},
{labels: labelMap{"queryid": "1500", "query": "select 1 from foo"}, metricType: dto.MetricType_COUNTER, value: 1},
}

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

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

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

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

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

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

ch := make(chan prometheus.Metric)
Expand All @@ -184,6 +187,9 @@ func TestPGStateStatementsCollectorNullWithStatement(t *testing.T) {
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_COUNTER, value: 0},
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
{labels: labelMap{"user": "unknown", "datname": "unknown", "queryid": "unknown"}, metricType: dto.MetricType_GAUGE, value: 0},
{labels: labelMap{"queryid": "unknown", "query": "unknown"}, metricType: dto.MetricType_COUNTER, value: 1},
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Expand Down