Skip to content
This repository has been archived by the owner on Aug 22, 2024. It is now read-only.

Commit

Permalink
postgres/locks: add metrics postgres_locks_all_in_flight, postgres_lo…
Browse files Browse the repository at this point in the history
…cks_not_granted_in_flight.
  • Loading branch information
lesovsky committed May 16, 2021
1 parent e9ebd90 commit 536e248
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 48 deletions.
128 changes: 97 additions & 31 deletions internal/collector/postgres_locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,51 @@ import (
)

const (
postgresLocksQuery = "SELECT mode, count(*) FROM pg_locks GROUP BY mode"
locksQuery = "SELECT " +
"count(*) FILTER (WHERE mode = 'AccessShareLock') AS access_share_lock, " +
"count(*) FILTER (WHERE mode = 'RowShareLock') AS row_share_lock, " +
"count(*) FILTER (WHERE mode = 'RowExclusiveLock') AS row_exclusive_lock, " +
"count(*) FILTER (WHERE mode = 'ShareUpdateExclusiveLock') AS share_update_exclusive_lock, " +
"count(*) FILTER (WHERE mode = 'ShareLock') AS share_lock, " +
"count(*) FILTER (WHERE mode = 'ShareRowExclusiveLock') AS share_row_exclusive_lock, " +
"count(*) FILTER (WHERE mode = 'ExclusiveLock') AS exclusive_lock, " +
"count(*) FILTER (WHERE mode = 'AccessExclusiveLock') AS access_exclusive_lock, " +
"count(*) FILTER (WHERE not granted) AS not_granted, " +
"count(*) AS total " +
"FROM pg_locks"
)

// postgresLocksCollector is a collector with locks related metrics descriptors.
type postgresLocksCollector struct {
modes typedDesc
locks typedDesc
locksAll typedDesc
notgranted typedDesc
}

// NewPostgresLocksCollector creates new postgresLocksCollector.
func NewPostgresLocksCollector(constLabels prometheus.Labels, _ model.CollectorSettings) (Collector, error) {
return &postgresLocksCollector{
modes: typedDesc{
locks: typedDesc{
desc: prometheus.NewDesc(
prometheus.BuildFQName("postgres", "locks", "in_flight"),
"Number of in-flight locks held by active processes in each mode.",
[]string{"mode"}, constLabels,
), valueType: prometheus.GaugeValue,
},
locksAll: typedDesc{
desc: prometheus.NewDesc(
prometheus.BuildFQName("postgres", "locks", "all_in_flight"),
"Total number of all in-flight locks held by active processes.",
nil, constLabels,
), valueType: prometheus.GaugeValue,
},
notgranted: typedDesc{
desc: prometheus.NewDesc(
prometheus.BuildFQName("postgres", "locks", "not_granted_in_flight"),
"Number of in-flight not granted locks held by active processes.",
nil, constLabels,
), valueType: prometheus.GaugeValue,
},
}, nil
}

Expand All @@ -39,50 +66,89 @@ func (c *postgresLocksCollector) Update(config Config, ch chan<- prometheus.Metr
defer conn.Close()

// get pg_stat_activity stats
res, err := conn.Query(postgresLocksQuery)
res, err := conn.Query(locksQuery)
if err != nil {
return err
}

// parse pg_stat_activity stats
stats := parsePostgresLocksStats(res)

for mode, value := range stats {
ch <- c.modes.newConstMetric(value, mode)
}
ch <- c.locks.newConstMetric(stats.accessShareLock, "AccessShareLock")
ch <- c.locks.newConstMetric(stats.rowShareLock, "RowShareLock")
ch <- c.locks.newConstMetric(stats.rowExclusiveLock, "RowExclusiveLock")
ch <- c.locks.newConstMetric(stats.shareUpdateExclusiveLock, "ShareUpdateExclusiveLock")
ch <- c.locks.newConstMetric(stats.shareLock, "ShareLock")
ch <- c.locks.newConstMetric(stats.shareRowExclusiveLock, "ShareRowExclusiveLock")
ch <- c.locks.newConstMetric(stats.exclusiveLock, "ExclusiveLock")
ch <- c.locks.newConstMetric(stats.accessExclusiveLock, "AccessExclusiveLock")
ch <- c.notgranted.newConstMetric(stats.notGranted)
ch <- c.locksAll.newConstMetric(stats.total)

return nil
}

// parsePostgresLocksStats parses result returned from Postgres and return stats map.
func parsePostgresLocksStats(r *model.PGResult) map[string]float64 {
// locksStat describes locks statistics.
type locksStat struct {
accessShareLock float64
rowShareLock float64
rowExclusiveLock float64
shareUpdateExclusiveLock float64
shareLock float64
shareRowExclusiveLock float64
exclusiveLock float64
accessExclusiveLock float64
notGranted float64
total float64
}

// parsePostgresLocksStats parses result returned from Postgres and return locks stats.
func parsePostgresLocksStats(r *model.PGResult) locksStat {
log.Debug("parse postgres locks stats")

stats := map[string]float64{
"AccessShareLock": 0,
"RowShareLock": 0,
"RowExclusiveLock": 0,
"ShareUpdateExclusiveLock": 0,
"ShareLock": 0,
"ShareRowExclusiveLock": 0,
"ExclusiveLock": 0,
"AccessExclusiveLock": 0,
}
stats := locksStat{}

for _, row := range r.Rows {
if len(row) != 2 {
log.Warn("invalid input: wrong number of columns, skip")
for i, colname := range r.Colnames {
// Skip empty (NULL) values.
if !row[i].Valid {
continue
}

// Get data value and convert it to float64 used by Prometheus.
v, err := strconv.ParseFloat(row[i].String, 64)
if err != nil {
log.Errorf("invalid input, parse '%s' failed: %s; skip", row[i].String, err)
continue
}

// Update stats struct
switch string(colname.Name) {
case "access_share_lock":
stats.accessShareLock = v
case "row_share_lock":
stats.rowShareLock = v
case "row_exclusive_lock":
stats.rowExclusiveLock = v
case "share_update_exclusive_lock":
stats.shareUpdateExclusiveLock = v
case "share_lock":
stats.shareLock = v
case "share_row_exclusive_lock":
stats.shareRowExclusiveLock = v
case "exclusive_lock":
stats.exclusiveLock = v
case "access_exclusive_lock":
stats.accessExclusiveLock = v
case "not_granted":
stats.notGranted = v
case "total":
stats.total = v
default:
log.Debugf("unsupported pg_locks stat column: %s, skip", string(colname.Name))
continue
}
}

mode := row[0].String

v, err := strconv.ParseFloat(row[1].String, 64)
if err != nil {
log.Errorf("invalid input, parse '%s' failed: %s; skip", row[1].String, err)
continue
}

stats[mode] = v
}

return stats
Expand Down
40 changes: 23 additions & 17 deletions internal/collector/postgres_locks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ func TestPostgresLocksCollector_Update(t *testing.T) {
var input = pipelineInput{
required: []string{
"postgres_locks_in_flight",
"postgres_locks_all_in_flight",
"postgres_locks_not_granted_in_flight",
},
collector: NewPostgresLocksCollector,
service: model.ServiceTypePostgresql,
Expand All @@ -24,30 +26,34 @@ func Test_parsePostgresLocksStats(t *testing.T) {
var testcases = []struct {
name string
res *model.PGResult
want map[string]float64
want locksStat
}{
{
name: "normal output",
res: &model.PGResult{
Nrows: 4,
Ncols: 2,
Colnames: []pgproto3.FieldDescription{{Name: []byte("mode")}, {Name: []byte("count")}},
Nrows: 1,
Ncols: 10,
Colnames: []pgproto3.FieldDescription{
{Name: []byte("access_share_lock")}, {Name: []byte("row_share_lock")},
{Name: []byte("row_exclusive_lock")}, {Name: []byte("share_update_exclusive_lock")},
{Name: []byte("share_lock")}, {Name: []byte("share_row_exclusive_lock")},
{Name: []byte("exclusive_lock")}, {Name: []byte("access_exclusive_lock")},
{Name: []byte("not_granted")}, {Name: []byte("total")},
},
Rows: [][]sql.NullString{
{{String: "RowExclusiveLock", Valid: true}, {String: "150", Valid: true}},
{{String: "RowShareLock", Valid: true}, {String: "100", Valid: true}},
{{String: "ExclusiveLock", Valid: true}, {String: "50", Valid: true}},
{{String: "AccessShareLock", Valid: true}, {String: "2000", Valid: true}},
{
{String: "11", Valid: true}, {String: "5", Valid: true},
{String: "4", Valid: true}, {String: "8", Valid: true},
{String: "7", Valid: true}, {String: "9", Valid: true},
{String: "1", Valid: true}, {String: "2", Valid: true},
{String: "6", Valid: true}, {String: "47", Valid: true},
},
},
},
want: map[string]float64{
"AccessShareLock": 2000,
"RowShareLock": 100,
"RowExclusiveLock": 150,
"ShareUpdateExclusiveLock": 0,
"ShareLock": 0,
"ShareRowExclusiveLock": 0,
"ExclusiveLock": 50,
"AccessExclusiveLock": 0,
want: locksStat{
accessShareLock: 11, rowShareLock: 5, rowExclusiveLock: 4, shareUpdateExclusiveLock: 8,
shareLock: 7, shareRowExclusiveLock: 9, exclusiveLock: 1, accessExclusiveLock: 2,
notGranted: 6, total: 47,
},
},
}
Expand Down

0 comments on commit 536e248

Please sign in to comment.