diff --git a/collector/pg_stat_statements.go b/collector/pg_stat_statements.go index 9160d3c16..cec8b1497 100644 --- a/collector/pg_stat_statements.go +++ b/collector/pg_stat_statements.go @@ -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"), @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 { diff --git a/collector/pg_stat_statements_test.go b/collector/pg_stat_statements_test.go index 0497ba380..cb0d14bb1 100644 --- a/collector/pg_stat_statements_test.go +++ b/collector/pg_stat_statements_test.go @@ -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) @@ -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) @@ -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}, } @@ -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) @@ -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) @@ -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}, } @@ -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) @@ -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) @@ -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}, } @@ -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) @@ -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) @@ -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}, }