mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-08 02:01:23 +00:00
Collect idle_in_transaction_time from PG >= 15
idle_in_transaction_time was added to pg_stat_database in pg 15 Signed-off-by: Alexander J. Maidak <amaidak@equinix.com>
This commit is contained in:
parent
3be4edccd4
commit
69c4119e19
@ -218,6 +218,15 @@ var (
|
|||||||
[]string{"datid", "datname"},
|
[]string{"datid", "datname"},
|
||||||
prometheus.Labels{},
|
prometheus.Labels{},
|
||||||
)
|
)
|
||||||
|
statDatabaseIdleInTransaction = prometheus.NewDesc(prometheus.BuildFQName(
|
||||||
|
namespace,
|
||||||
|
statDatabaseSubsystem,
|
||||||
|
"idle_in_transaction_time_seconds_total",
|
||||||
|
),
|
||||||
|
"Time spent idling while in a transaction in this database, in seconds",
|
||||||
|
[]string{"datid", "datname"},
|
||||||
|
prometheus.Labels{},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func statDatabaseQuery(columns []string) string {
|
func statDatabaseQuery(columns []string) string {
|
||||||
@ -254,6 +263,11 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
|
|||||||
columns = append(columns, "active_time")
|
columns = append(columns, "active_time")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idleInTransactionTimeAvail := instance.version.GTE(semver.MustParse("15.0.0"))
|
||||||
|
if idleInTransactionTimeAvail {
|
||||||
|
columns = append(columns, "idle_in_transaction_time")
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := db.QueryContext(ctx,
|
rows, err := db.QueryContext(ctx,
|
||||||
statDatabaseQuery(columns),
|
statDatabaseQuery(columns),
|
||||||
)
|
)
|
||||||
@ -264,7 +278,7 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var datid, datname sql.NullString
|
var datid, datname sql.NullString
|
||||||
var numBackends, xactCommit, xactRollback, blksRead, blksHit, tupReturned, tupFetched, tupInserted, tupUpdated, tupDeleted, conflicts, tempFiles, tempBytes, deadlocks, blkReadTime, blkWriteTime, activeTime sql.NullFloat64
|
var numBackends, xactCommit, xactRollback, blksRead, blksHit, tupReturned, tupFetched, tupInserted, tupUpdated, tupDeleted, conflicts, tempFiles, tempBytes, deadlocks, blkReadTime, blkWriteTime, activeTime, idleInTransactionTime sql.NullFloat64
|
||||||
var statsReset sql.NullTime
|
var statsReset sql.NullTime
|
||||||
|
|
||||||
r := []any{
|
r := []any{
|
||||||
@ -293,6 +307,10 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
|
|||||||
r = append(r, &activeTime)
|
r = append(r, &activeTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if idleInTransactionTimeAvail {
|
||||||
|
r = append(r, &idleInTransactionTime)
|
||||||
|
}
|
||||||
|
|
||||||
err := rows.Scan(r...)
|
err := rows.Scan(r...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -375,6 +393,11 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if idleInTransactionTimeAvail && !idleInTransactionTime.Valid {
|
||||||
|
level.Debug(c.log).Log("msg", "Skipping collecting metric because it has no idle_in_transaction_time")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
statsResetMetric := 0.0
|
statsResetMetric := 0.0
|
||||||
if !statsReset.Valid {
|
if !statsReset.Valid {
|
||||||
level.Debug(c.log).Log("msg", "No metric for stats_reset, will collect 0 instead")
|
level.Debug(c.log).Log("msg", "No metric for stats_reset, will collect 0 instead")
|
||||||
@ -512,6 +535,15 @@ func (c *PGStatDatabaseCollector) Update(ctx context.Context, instance *instance
|
|||||||
labels...,
|
labels...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if idleInTransactionTimeAvail {
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
statDatabaseIdleInTransaction,
|
||||||
|
prometheus.CounterValue,
|
||||||
|
idleInTransactionTime.Float64/1000.0,
|
||||||
|
labels...,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -132,6 +132,116 @@ func TestPGStatDatabaseCollector(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGStatDatabaseCollectorCollectsIdleTime(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, version: semver.MustParse("15.0.0")}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"datid",
|
||||||
|
"datname",
|
||||||
|
"numbackends",
|
||||||
|
"xact_commit",
|
||||||
|
"xact_rollback",
|
||||||
|
"blks_read",
|
||||||
|
"blks_hit",
|
||||||
|
"tup_returned",
|
||||||
|
"tup_fetched",
|
||||||
|
"tup_inserted",
|
||||||
|
"tup_updated",
|
||||||
|
"tup_deleted",
|
||||||
|
"conflicts",
|
||||||
|
"temp_files",
|
||||||
|
"temp_bytes",
|
||||||
|
"deadlocks",
|
||||||
|
"blk_read_time",
|
||||||
|
"blk_write_time",
|
||||||
|
"stats_reset",
|
||||||
|
"active_time",
|
||||||
|
"idle_in_transaction_time",
|
||||||
|
}
|
||||||
|
|
||||||
|
srT, err := time.Parse("2006-01-02 15:04:05.00000-07", "2023-05-25 17:10:42.81132-07")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing time: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows(columns).
|
||||||
|
AddRow(
|
||||||
|
"pid",
|
||||||
|
"postgres",
|
||||||
|
354,
|
||||||
|
4945,
|
||||||
|
289097744,
|
||||||
|
1242257,
|
||||||
|
int64(3275602074),
|
||||||
|
89320867,
|
||||||
|
450139,
|
||||||
|
2034563757,
|
||||||
|
0,
|
||||||
|
int64(2725688749),
|
||||||
|
23,
|
||||||
|
52,
|
||||||
|
74,
|
||||||
|
925,
|
||||||
|
16,
|
||||||
|
823,
|
||||||
|
srT,
|
||||||
|
33,
|
||||||
|
123,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock.ExpectQuery(sanitizeQuery(statDatabaseQuery(columns))).WillReturnRows(rows)
|
||||||
|
|
||||||
|
ch := make(chan prometheus.Metric)
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
c := PGStatDatabaseCollector{
|
||||||
|
log: log.With(log.NewNopLogger(), "collector", "pg_stat_database"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Update(context.Background(), inst, ch); err != nil {
|
||||||
|
t.Errorf("Error calling PGStatDatabaseCollector.Update: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
expected := []MetricResult{
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_GAUGE, value: 354},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 4945},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 289097744},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1242257},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 3275602074},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 89320867},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 450139},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2034563757},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 0},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 2725688749},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 23},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 52},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 74},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 925},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 16},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 823},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 1685059842},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 0.033},
|
||||||
|
{labels: labelMap{"datid": "pid", "datname": "postgres"}, metricType: dto.MetricType_COUNTER, value: 0.123},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPGStatDatabaseCollectorNullValues(t *testing.T) {
|
func TestPGStatDatabaseCollectorNullValues(t *testing.T) {
|
||||||
db, mock, err := sqlmock.New()
|
db, mock, err := sqlmock.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user