Skip to content

Commit 6e11abe

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 6290786 commit 6e11abe

File tree

5 files changed

+183
-35
lines changed

5 files changed

+183
-35
lines changed

Diff for: 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).

Diff for: 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")},

Diff for: 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"),

Diff for: collector/pg_locks.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
19+
"github.com/go-kit/log"
20+
"github.com/prometheus/client_golang/prometheus"
21+
)
22+
23+
const locksSubsystem = "locks"
24+
25+
func init() {
26+
registerCollector(locksSubsystem, defaultEnabled, NewPGLocksCollector)
27+
}
28+
29+
type PGLocksCollector struct {
30+
log log.Logger
31+
}
32+
33+
func NewPGLocksCollector(config collectorConfig) (Collector, error) {
34+
return &PGLocksCollector{
35+
log: config.logger,
36+
}, nil
37+
}
38+
39+
var (
40+
pgLocksDesc = prometheus.NewDesc(
41+
prometheus.BuildFQName(
42+
namespace,
43+
locksSubsystem,
44+
"count",
45+
),
46+
"Number of locks",
47+
[]string{"datname", "mode"}, nil,
48+
)
49+
50+
pgLocksQuery = `
51+
SELECT
52+
pg_database.datname as datname,
53+
tmp.mode as mode,
54+
COALESCE(count, 0) as count
55+
FROM
56+
(
57+
VALUES
58+
('accesssharelock'),
59+
('rowsharelock'),
60+
('rowexclusivelock'),
61+
('shareupdateexclusivelock'),
62+
('sharelock'),
63+
('sharerowexclusivelock'),
64+
('exclusivelock'),
65+
('accessexclusivelock'),
66+
('sireadlock')
67+
) AS tmp(mode)
68+
CROSS JOIN pg_database
69+
LEFT JOIN (
70+
SELECT
71+
database,
72+
lower(mode) AS mode,
73+
count(*) AS count
74+
FROM
75+
pg_locks
76+
WHERE
77+
database IS NOT NULL
78+
GROUP BY
79+
database,
80+
lower(mode)
81+
) AS tmp2 ON tmp.mode = tmp2.mode
82+
and pg_database.oid = tmp2.database
83+
ORDER BY
84+
1
85+
`
86+
)
87+
88+
// Update implements Collector and exposes database locks.
89+
// It is called by the Prometheus registry when collecting metrics.
90+
func (c PGLocksCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
91+
db := instance.getDB()
92+
// Query the list of databases
93+
rows, err := db.QueryContext(ctx,
94+
pgLocksQuery,
95+
)
96+
if err != nil {
97+
return err
98+
}
99+
defer rows.Close()
100+
101+
var datname, mode string
102+
var count int64
103+
104+
for rows.Next() {
105+
if err := rows.Scan(&datname, &mode, &count); err != nil {
106+
return err
107+
}
108+
109+
ch <- prometheus.MustNewConstMetric(
110+
pgLocksDesc,
111+
prometheus.GaugeValue, float64(count),
112+
datname, mode,
113+
)
114+
}
115+
if err := rows.Err(); err != nil {
116+
return err
117+
}
118+
return nil
119+
}

Diff for: 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)