Skip to content
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
15 changes: 12 additions & 3 deletions collector/pg_long_running_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package collector

import (
"context"
"database/sql"
"log/slog"

"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -50,7 +51,7 @@ var (
)

longRunningTransactionsQuery = `
SELECT
SELECT
COUNT(*) as transactions,
MAX(EXTRACT(EPOCH FROM clock_timestamp() - pg_stat_activity.xact_start)) AS oldest_timestamp_seconds
FROM pg_catalog.pg_stat_activity
Expand All @@ -72,12 +73,20 @@ func (PGLongRunningTransactionsCollector) Update(ctx context.Context, instance *
defer rows.Close()

for rows.Next() {
var transactions, ageInSeconds float64
var transactions float64
var ageInSeconds sql.NullFloat64

if err := rows.Scan(&transactions, &ageInSeconds); err != nil {
return err
}

// If there are no long running transactions, ageInSeconds will be NULL
// so we set it to 0
age := 0.0
if ageInSeconds.Valid {
age = ageInSeconds.Float64
}

ch <- prometheus.MustNewConstMetric(
longRunningTransactionsCount,
prometheus.GaugeValue,
Expand All @@ -86,7 +95,7 @@ func (PGLongRunningTransactionsCollector) Update(ctx context.Context, instance *
ch <- prometheus.MustNewConstMetric(
longRunningTransactionsAgeInSeconds,
prometheus.GaugeValue,
ageInSeconds,
age,
)
}
if err := rows.Err(); err != nil {
Expand Down
41 changes: 41 additions & 0 deletions collector/pg_long_running_transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,44 @@ func TestPGLongRunningTransactionsCollector(t *testing.T) {
t.Errorf("there were unfulfilled exceptions: %s", err)
}
}

func TestPGLongRunningTransactionsCollectorNull(t *testing.T) {
// Test when no long running transactions are present
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{
"transactions",
"age_in_seconds",
}
rows := sqlmock.NewRows(columns).
AddRow(0, nil)

mock.ExpectQuery(sanitizeQuery(longRunningTransactionsQuery)).WillReturnRows(rows)

ch := make(chan prometheus.Metric)
go func() {
defer close(ch)
c := PGLongRunningTransactionsCollector{}

if err := c.Update(context.Background(), inst, ch); err != nil {
t.Errorf("Error calling PGLongRunningTransactionsCollector.Update: %s", err)
}
}()
expected := []MetricResult{
{labels: labelMap{}, value: 0, metricType: dto.MetricType_GAUGE},
{labels: labelMap{}, value: 0, 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)
}
}