Skip to content

Commit c3e9c0e

Browse files
authored
Merge branch 'master' into cleanup/gitlab-exporter-long-xacts
2 parents 1a3fdaf + 04bb60c commit c3e9c0e

15 files changed

+1248
-191
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.13.2 / 2023-07-21
2+
3+
* [BUGFIX] Fix type issues on pg_postmaster metrics #828
4+
* [BUGFIX] Fix pg_replication collector instantiation #854
5+
* [BUGFIX] Fix pg_process_idle metrics #855
6+
17
## 0.13.1 / 2023-06-27
28

39
* [BUGFIX] Make collectors not fail on null values #823

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ To use the multi-target functionality, send an http request to the endpoint `/pr
3030

3131
To avoid putting sensitive information like username and password in the URL, preconfigured auth modules are supported via the [auth_modules](#auth_modules) section of the config file. auth_modules for DSNs can be used with the `/probe` endpoint by specifying the `?auth_module=foo` http parameter.
3232

33+
Example Prometheus config:
34+
```yaml
35+
scrape_configs:
36+
- job_name: 'postgres'
37+
static_configs:
38+
- targets:
39+
- server1:5432
40+
- server2:5432
41+
metrics_path: /probe
42+
params:
43+
auth_module: [foo]
44+
relabel_configs:
45+
- source_labels: [__address__]
46+
target_label: __param_target
47+
- source_labels: [__param_target]
48+
target_label: instance
49+
- target_label: __address__
50+
replacement: 127.0.0.1:9116 # The postgres exporter's real hostname:port.
51+
```
52+
3353
## Configuration File
3454
3555
The configuration file controls the behavior of the exporter. It can be set using the `--config.file` command line flag and defaults to `postgres_exporter.yml`.

collector/collector_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func readMetric(m prometheus.Metric) MetricResult {
4949
func sanitizeQuery(q string) string {
5050
q = strings.Join(strings.Fields(q), " ")
5151
q = strings.Replace(q, "(", "\\(", -1)
52+
q = strings.Replace(q, "?", "\\?", -1)
5253
q = strings.Replace(q, ")", "\\)", -1)
5354
q = strings.Replace(q, "[", "\\[", -1)
5455
q = strings.Replace(q, "]", "\\]", -1)

collector/pg_database_wraparound.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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/go-kit/log/level"
22+
"github.com/prometheus/client_golang/prometheus"
23+
)
24+
25+
const databaseWraparoundSubsystem = "database_wraparound"
26+
27+
func init() {
28+
registerCollector(databaseWraparoundSubsystem, defaultDisabled, NewPGDatabaseWraparoundCollector)
29+
}
30+
31+
type PGDatabaseWraparoundCollector struct {
32+
log log.Logger
33+
}
34+
35+
func NewPGDatabaseWraparoundCollector(config collectorConfig) (Collector, error) {
36+
return &PGDatabaseWraparoundCollector{log: config.logger}, nil
37+
}
38+
39+
var (
40+
databaseWraparoundAgeDatfrozenxid = prometheus.NewDesc(
41+
prometheus.BuildFQName(namespace, databaseWraparoundSubsystem, "age_datfrozenxid_seconds"),
42+
"Age of the oldest transaction ID that has not been frozen.",
43+
[]string{"datname"},
44+
prometheus.Labels{},
45+
)
46+
databaseWraparoundAgeDatminmxid = prometheus.NewDesc(
47+
prometheus.BuildFQName(namespace, databaseWraparoundSubsystem, "age_datminmxid_seconds"),
48+
"Age of the oldest multi-transaction ID that has been replaced with a transaction ID.",
49+
[]string{"datname"},
50+
prometheus.Labels{},
51+
)
52+
53+
databaseWraparoundQuery = `
54+
SELECT
55+
datname,
56+
age(d.datfrozenxid) as age_datfrozenxid,
57+
mxid_age(d.datminmxid) as age_datminmxid
58+
FROM
59+
pg_catalog.pg_database d
60+
WHERE
61+
d.datallowconn
62+
`
63+
)
64+
65+
func (c *PGDatabaseWraparoundCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
66+
db := instance.getDB()
67+
rows, err := db.QueryContext(ctx,
68+
databaseWraparoundQuery)
69+
70+
if err != nil {
71+
return err
72+
}
73+
defer rows.Close()
74+
75+
for rows.Next() {
76+
var datname sql.NullString
77+
var ageDatfrozenxid, ageDatminmxid sql.NullFloat64
78+
79+
if err := rows.Scan(&datname, &ageDatfrozenxid, &ageDatminmxid); err != nil {
80+
return err
81+
}
82+
83+
if !datname.Valid {
84+
level.Debug(c.log).Log("msg", "Skipping database with NULL name")
85+
continue
86+
}
87+
if !ageDatfrozenxid.Valid {
88+
level.Debug(c.log).Log("msg", "Skipping stat emission with NULL age_datfrozenxid")
89+
continue
90+
}
91+
if !ageDatminmxid.Valid {
92+
level.Debug(c.log).Log("msg", "Skipping stat emission with NULL age_datminmxid")
93+
continue
94+
}
95+
96+
ageDatfrozenxidMetric := ageDatfrozenxid.Float64
97+
98+
ch <- prometheus.MustNewConstMetric(
99+
databaseWraparoundAgeDatfrozenxid,
100+
prometheus.GaugeValue,
101+
ageDatfrozenxidMetric, datname.String,
102+
)
103+
104+
ageDatminmxidMetric := ageDatminmxid.Float64
105+
ch <- prometheus.MustNewConstMetric(
106+
databaseWraparoundAgeDatminmxid,
107+
prometheus.GaugeValue,
108+
ageDatminmxidMetric, datname.String,
109+
)
110+
}
111+
if err := rows.Err(); err != nil {
112+
return err
113+
}
114+
return nil
115+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 TestPGDatabaseWraparoundCollector(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+
inst := &instance{db: db}
32+
columns := []string{
33+
"datname",
34+
"age_datfrozenxid",
35+
"age_datminmxid",
36+
}
37+
rows := sqlmock.NewRows(columns).
38+
AddRow("newreddit", 87126426, 0)
39+
40+
mock.ExpectQuery(sanitizeQuery(databaseWraparoundQuery)).WillReturnRows(rows)
41+
42+
ch := make(chan prometheus.Metric)
43+
go func() {
44+
defer close(ch)
45+
c := PGDatabaseWraparoundCollector{}
46+
47+
if err := c.Update(context.Background(), inst, ch); err != nil {
48+
t.Errorf("Error calling PGDatabaseWraparoundCollector.Update: %s", err)
49+
}
50+
}()
51+
expected := []MetricResult{
52+
{labels: labelMap{"datname": "newreddit"}, value: 87126426, metricType: dto.MetricType_GAUGE},
53+
{labels: labelMap{"datname": "newreddit"}, value: 0, metricType: dto.MetricType_GAUGE},
54+
}
55+
convey.Convey("Metrics comparison", t, func() {
56+
for _, expect := range expected {
57+
m := readMetric(<-ch)
58+
convey.So(expect, convey.ShouldResemble, m)
59+
}
60+
})
61+
if err := mock.ExpectationsWereMet(); err != nil {
62+
t.Errorf("there were unfulfilled exceptions: %s", err)
63+
}
64+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 statActivityAutovacuumSubsystem = "stat_activity_autovacuum"
24+
25+
func init() {
26+
registerCollector(statActivityAutovacuumSubsystem, defaultDisabled, NewPGStatActivityAutovacuumCollector)
27+
}
28+
29+
type PGStatActivityAutovacuumCollector struct {
30+
log log.Logger
31+
}
32+
33+
func NewPGStatActivityAutovacuumCollector(config collectorConfig) (Collector, error) {
34+
return &PGStatActivityAutovacuumCollector{log: config.logger}, nil
35+
}
36+
37+
var (
38+
statActivityAutovacuumAgeInSeconds = prometheus.NewDesc(
39+
prometheus.BuildFQName(namespace, statActivityAutovacuumSubsystem, "timestamp_seconds"),
40+
"Start timestamp of the vacuum process in seconds",
41+
[]string{"relname"},
42+
prometheus.Labels{},
43+
)
44+
45+
statActivityAutovacuumQuery = `
46+
SELECT
47+
SPLIT_PART(query, '.', 2) AS relname,
48+
EXTRACT(xact_start) AS timestamp_seconds
49+
FROM
50+
pg_catalog.pg_stat_activity
51+
WHERE
52+
query LIKE 'autovacuum:%'
53+
`
54+
)
55+
56+
func (PGStatActivityAutovacuumCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
57+
db := instance.getDB()
58+
rows, err := db.QueryContext(ctx,
59+
statActivityAutovacuumQuery)
60+
61+
if err != nil {
62+
return err
63+
}
64+
defer rows.Close()
65+
66+
for rows.Next() {
67+
var relname string
68+
var ageInSeconds float64
69+
70+
if err := rows.Scan(&relname, &ageInSeconds); err != nil {
71+
return err
72+
}
73+
74+
ch <- prometheus.MustNewConstMetric(
75+
statActivityAutovacuumAgeInSeconds,
76+
prometheus.GaugeValue,
77+
ageInSeconds, relname,
78+
)
79+
}
80+
if err := rows.Err(); err != nil {
81+
return err
82+
}
83+
return nil
84+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 TestPGStatActivityAutovacuumCollector(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+
inst := &instance{db: db}
32+
columns := []string{
33+
"relname",
34+
"timestamp_seconds",
35+
}
36+
rows := sqlmock.NewRows(columns).
37+
AddRow("test", 3600)
38+
39+
mock.ExpectQuery(sanitizeQuery(statActivityAutovacuumQuery)).WillReturnRows(rows)
40+
41+
ch := make(chan prometheus.Metric)
42+
go func() {
43+
defer close(ch)
44+
c := PGStatActivityAutovacuumCollector{}
45+
46+
if err := c.Update(context.Background(), inst, ch); err != nil {
47+
t.Errorf("Error calling PGStatActivityAutovacuumCollector.Update: %s", err)
48+
}
49+
}()
50+
expected := []MetricResult{
51+
{labels: labelMap{"relname": "test"}, value: 3600, metricType: dto.MetricType_GAUGE},
52+
}
53+
convey.Convey("Metrics comparison", t, func() {
54+
for _, expect := range expected {
55+
m := readMetric(<-ch)
56+
convey.So(expect, convey.ShouldResemble, m)
57+
}
58+
})
59+
if err := mock.ExpectationsWereMet(); err != nil {
60+
t.Errorf("there were unfulfilled exceptions: %s", err)
61+
}
62+
}

0 commit comments

Comments
 (0)