Skip to content

Commit 565228d

Browse files
committed
Migrate pg_locks to collector package
Migrate the `pg_locks_count` query from `main` to the `collector` package. Signed-off-by: SuperQ <superq@gmail.com>
1 parent 6a1bb59 commit 565228d

File tree

5 files changed

+193
-35
lines changed

5 files changed

+193
-35
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ This will build the docker image as `prometheuscommunity/postgres_exporter:${bra
7373

7474

7575
* `[no-]collector.database`
76-
Enable the database collector (default: enabled).
76+
Enable the `database` collector (default: enabled).
77+
78+
* `[no-]collector.locks`
79+
Enable the `locks` collector (default: enabled).
7780

7881
* `[no-]collector.postmaster`
7982
Enable the `postmaster` collector (default: enabled).

cmd/postgres_exporter/postgres_exporter.go

-9
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,6 @@ var builtinMetricMaps = map[string]intermediateMetricMap{
176176
true,
177177
0,
178178
},
179-
"pg_locks": {
180-
map[string]ColumnMapping{
181-
"datname": {LABEL, "Name of this database", nil, nil},
182-
"mode": {LABEL, "Type of Lock", nil, nil},
183-
"count": {GAUGE, "Number of locks", nil, nil},
184-
},
185-
true,
186-
0,
187-
},
188179
"pg_stat_replication": {
189180
map[string]ColumnMapping{
190181
"procpid": {DISCARD, "Process ID of a WAL sender process", nil, semver.MustParseRange("<9.2.0")},

cmd/postgres_exporter/queries.go

-25
Original file line numberDiff line numberDiff line change
@@ -46,31 +46,6 @@ type OverrideQuery struct {
4646
// Overriding queries for namespaces above.
4747
// TODO: validate this is a closed set in tests, and there are no overlaps
4848
var queryOverrides = map[string][]OverrideQuery{
49-
"pg_locks": {
50-
{
51-
semver.MustParseRange(">0.0.0"),
52-
`SELECT pg_database.datname,tmp.mode,COALESCE(count,0) as count
53-
FROM
54-
(
55-
VALUES ('accesssharelock'),
56-
('rowsharelock'),
57-
('rowexclusivelock'),
58-
('shareupdateexclusivelock'),
59-
('sharelock'),
60-
('sharerowexclusivelock'),
61-
('exclusivelock'),
62-
('accessexclusivelock'),
63-
('sireadlock')
64-
) AS tmp(mode) CROSS JOIN pg_database
65-
LEFT JOIN
66-
(SELECT database, lower(mode) AS mode,count(*) AS count
67-
FROM pg_locks WHERE database IS NOT NULL
68-
GROUP BY database, lower(mode)
69-
) AS tmp2
70-
ON tmp.mode=tmp2.mode and pg_database.oid = tmp2.database ORDER BY 1`,
71-
},
72-
},
73-
7449
"pg_stat_replication": {
7550
{
7651
semver.MustParseRange(">=10.0.0"),

collector/pg_locks.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"context"
18+
"database/sql"
19+
20+
"github.com/go-kit/log"
21+
"github.com/prometheus/client_golang/prometheus"
22+
)
23+
24+
const locksSubsystem = "locks"
25+
26+
func init() {
27+
registerCollector(locksSubsystem, defaultEnabled, NewPGLocksCollector)
28+
}
29+
30+
type PGLocksCollector struct {
31+
log log.Logger
32+
}
33+
34+
func NewPGLocksCollector(config collectorConfig) (Collector, error) {
35+
return &PGLocksCollector{
36+
log: config.logger,
37+
}, nil
38+
}
39+
40+
var (
41+
pgLocksDesc = prometheus.NewDesc(
42+
prometheus.BuildFQName(
43+
namespace,
44+
locksSubsystem,
45+
"count",
46+
),
47+
"Number of locks",
48+
[]string{"datname", "mode"}, nil,
49+
)
50+
51+
pgLocksQuery = `
52+
SELECT
53+
pg_database.datname as datname,
54+
tmp.mode as mode,
55+
COALESCE(count, 0) as count
56+
FROM
57+
(
58+
VALUES
59+
('accesssharelock'),
60+
('rowsharelock'),
61+
('rowexclusivelock'),
62+
('shareupdateexclusivelock'),
63+
('sharelock'),
64+
('sharerowexclusivelock'),
65+
('exclusivelock'),
66+
('accessexclusivelock'),
67+
('sireadlock')
68+
) AS tmp(mode)
69+
CROSS JOIN pg_database
70+
LEFT JOIN (
71+
SELECT
72+
database,
73+
lower(mode) AS mode,
74+
count(*) AS count
75+
FROM
76+
pg_locks
77+
WHERE
78+
database IS NOT NULL
79+
GROUP BY
80+
database,
81+
lower(mode)
82+
) AS tmp2 ON tmp.mode = tmp2.mode
83+
and pg_database.oid = tmp2.database
84+
ORDER BY
85+
1
86+
`
87+
)
88+
89+
// Update implements Collector and exposes database locks.
90+
// It is called by the Prometheus registry when collecting metrics.
91+
func (c PGLocksCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
92+
db := instance.getDB()
93+
// Query the list of databases
94+
rows, err := db.QueryContext(ctx,
95+
pgLocksQuery,
96+
)
97+
if err != nil {
98+
return err
99+
}
100+
defer rows.Close()
101+
102+
var datname, mode sql.NullString
103+
var count sql.NullInt64
104+
105+
for rows.Next() {
106+
if err := rows.Scan(&datname, &mode, &count); err != nil {
107+
return err
108+
}
109+
110+
if !datname.Valid || !mode.Valid {
111+
continue
112+
}
113+
114+
countMetric := 0.0
115+
if count.Valid {
116+
countMetric = float64(count.Int64)
117+
}
118+
119+
ch <- prometheus.MustNewConstMetric(
120+
pgLocksDesc,
121+
prometheus.GaugeValue, countMetric,
122+
datname.String, mode.String,
123+
)
124+
}
125+
if err := rows.Err(); err != nil {
126+
return err
127+
}
128+
return nil
129+
}

collector/pg_locks_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
package collector
14+
15+
import (
16+
"context"
17+
"testing"
18+
19+
"github.com/DATA-DOG/go-sqlmock"
20+
"github.com/prometheus/client_golang/prometheus"
21+
dto "github.com/prometheus/client_model/go"
22+
"github.com/smartystreets/goconvey/convey"
23+
)
24+
25+
func TestPGLocksCollector(t *testing.T) {
26+
db, mock, err := sqlmock.New()
27+
if err != nil {
28+
t.Fatalf("Error opening a stub db connection: %s", err)
29+
}
30+
defer db.Close()
31+
32+
inst := &instance{db: db}
33+
34+
rows := sqlmock.NewRows([]string{"datname", "mode", "count"}).
35+
AddRow("test", "exclusivelock", 42)
36+
37+
mock.ExpectQuery(sanitizeQuery(pgLocksQuery)).WillReturnRows(rows)
38+
39+
ch := make(chan prometheus.Metric)
40+
go func() {
41+
defer close(ch)
42+
c := PGLocksCollector{}
43+
if err := c.Update(context.Background(), inst, ch); err != nil {
44+
t.Errorf("Error calling PGLocksCollector.Update: %s", err)
45+
}
46+
}()
47+
48+
expected := []MetricResult{
49+
{labels: labelMap{"datname": "test", "mode": "exclusivelock"}, value: 42, metricType: dto.MetricType_GAUGE},
50+
}
51+
convey.Convey("Metrics comparison", t, func() {
52+
for _, expect := range expected {
53+
m := readMetric(<-ch)
54+
convey.So(expect, convey.ShouldResemble, m)
55+
}
56+
})
57+
if err := mock.ExpectationsWereMet(); err != nil {
58+
t.Errorf("there were unfulfilled exceptions: %s", err)
59+
}
60+
}

0 commit comments

Comments
 (0)