1
0
mirror of https://github.com/prometheus-community/postgres_exporter.git synced 2026-10-23 12:08:57 +03:00

fix: skip autovacuum metric when xact_start is NULL (#1381)

The stat_activity_autovacuum collector scanned xact_start into a plain
float64, so a NULL value (possible for autovacuum workers) failed the
whole collector with "converting NULL to float64 is unsupported". Scan
into sql.NullFloat64 and skip the row when it is NULL, matching the
pg_stat_walreceiver / pg_stat_database collectors.

Fixes #952

Signed-off-by: Dean Chen <862469039@qq.com>
This commit is contained in:
Dean Chen
2026-09-09 17:41:27 +05:00
committed by GitHub
parent bbacd7fff2
commit dfcfbb7f0f
2 changed files with 50 additions and 3 deletions

View File

@@ -15,6 +15,7 @@ package collector
import (
"context"
"database/sql"
"log/slog"
"github.com/prometheus/client_golang/prometheus"
@@ -51,7 +52,7 @@ var (
`
)
func (PGStatActivityAutovacuumCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
func (c PGStatActivityAutovacuumCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
db := instance.getDB()
rows, err := db.QueryContext(ctx,
statActivityAutovacuumQuery)
@@ -63,16 +64,21 @@ func (PGStatActivityAutovacuumCollector) Update(ctx context.Context, instance *i
for rows.Next() {
var relname string
var ageInSeconds float64
var ageInSeconds sql.NullFloat64
if err := rows.Scan(&relname, &ageInSeconds); err != nil {
return err
}
if !ageInSeconds.Valid {
c.log.Debug("Skipping collecting metric because it has no xact_start")
continue
}
ch <- prometheus.MustNewConstMetric(
statActivityAutovacuumAgeInSeconds,
prometheus.GaugeValue,
ageInSeconds, relname,
ageInSeconds.Float64, relname,
)
}
if err := rows.Err(); err != nil {

View File

@@ -19,6 +19,7 @@ import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/promslog"
"github.com/smartystreets/goconvey/convey"
)
@@ -60,3 +61,43 @@ func TestPGStatActivityAutovacuumCollector(t *testing.T) {
t.Errorf("there were unfulfilled exceptions: %s", err)
}
}
func TestPGStatActivityAutovacuumCollectorNullTimestamp(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Error opening a stub db connection: %s", err)
}
defer db.Close()
inst := &instance{db: db}
columns := []string{
"relname",
"timestamp_seconds",
}
rows := sqlmock.NewRows(columns).
AddRow("no_xact_start", nil).
AddRow("test", 3600)
mock.ExpectQuery(sanitizeQuery(statActivityAutovacuumQuery)).WillReturnRows(rows)
ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
c := PGStatActivityAutovacuumCollector{log: promslog.NewNopLogger()}
if err := c.Update(context.Background(), inst, ch); err != nil {
t.Errorf("Error calling PGStatActivityAutovacuumCollector.Update: %s", err)
}
}()
expected := []MetricResult{
{labels: labelMap{"relname": "test"}, value: 3600, metricType: dto.MetricType_GAUGE},
}
convey.Convey("Metrics comparison", t, func() {
for _, expect := range expected {
m := readMetric(<-ch)
convey.So(expect, convey.ShouldResemble, m)
}
})
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled exceptions: %s", err)
}
}