diff --git a/collector/pg_stat_activity_autovacuum.go b/collector/pg_stat_activity_autovacuum.go index 40f43539..10350b5b 100644 --- a/collector/pg_stat_activity_autovacuum.go +++ b/collector/pg_stat_activity_autovacuum.go @@ -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 { diff --git a/collector/pg_stat_activity_autovacuum_test.go b/collector/pg_stat_activity_autovacuum_test.go index a6fcdbca..667f2d73 100644 --- a/collector/pg_stat_activity_autovacuum_test.go +++ b/collector/pg_stat_activity_autovacuum_test.go @@ -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) + } +}