Skip to content

Commit e0a5117

Browse files
committed
Merge remote-tracking branch 'grafana/master' into exporter-package-v0.15.0
2 parents 9358270 + bcc02ce commit e0a5117

21 files changed

+1186
-689
lines changed

.github/workflows/golangci-lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ jobs:
1818
runs-on: ubuntu-latest
1919
steps:
2020
- name: Checkout repository
21-
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
21+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
2222
- name: install Go
2323
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
2424
with:
25-
go-version: 1.20.x
25+
go-version: 1.21.x
2626
- name: Install snmp_exporter/generator dependencies
2727
run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
2828
if: github.repository == 'prometheus/snmp_exporter'

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,29 @@ docker run --net=host -it --rm -e POSTGRES_PASSWORD=password postgres
1717
# Connect to it
1818
docker run \
1919
--net=host \
20-
-e DATA_SOURCE_NAME="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable" \
20+
-e DATA_SOURCE_URI="localhost:5432/postgres?sslmode=disable" \
21+
-e DATA_SOURCE_USER=postgres \
22+
-e DATA_SOURCE_PASS=password \
2123
quay.io/prometheuscommunity/postgres-exporter
2224
```
2325

26+
Test with:
27+
```bash
28+
curl "http://localhost:9187/metrics"
29+
```
30+
31+
Example Prometheus config:
32+
```yaml
33+
scrape_configs:
34+
- job_name: postgres
35+
static_configs:
36+
- targets: ["127.0.0.1:9187"] # Replace IP with the hostname of the docker container if you're running the container in a separate network
37+
```
38+
39+
Now use the DATA_SOURCE_PASS_FILE with a mounted file containing the password to prevent having the password in an environment variable.
40+
41+
The container process runs with uid/gid 65534 (important for file permissions).
42+
2443
## Multi-Target Support (BETA)
2544
**This Feature is in beta and may require changes in future releases. Feedback is welcome.**
2645
@@ -208,7 +227,7 @@ The following environment variables configure the exporter:
208227
* `DATA_SOURCE_URI`
209228
an alternative to `DATA_SOURCE_NAME` which exclusively accepts the hostname
210229
without a username and password component. For example, `my_pg_hostname` or
211-
`my_pg_hostname?sslmode=disable`.
230+
`my_pg_hostname:5432/postgres?sslmode=disable`.
212231

213232
* `DATA_SOURCE_URI_FILE`
214233
The same as above but reads the URI from a file.

cmd/postgres_exporter/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func main() {
9494
}
9595

9696
excludedDatabases := strings.Split(*excludeDatabases, ",")
97-
rootFallbackLogger.Log("msg", "Excluded databases", "databases", fmt.Sprintf("%v", excludedDatabases))
97+
level.Info(rootFallbackLogger).Log("msg", "Excluded databases", "databases", fmt.Sprintf("%v", excludedDatabases))
9898

9999
if *queriesPath != "" {
100100
level.Warn(rootFallbackLogger).Log("msg", "The extended queries.yaml config is DEPRECATED", "file", *queriesPath)
@@ -142,6 +142,7 @@ func main() {
142142
if err != nil {
143143
level.Warn(rootFallbackLogger).Log("msg", "Failed to create PostgresCollector", "err", err.Error())
144144
} else {
145+
defer pe.Close()
145146
prometheus.MustRegister(pe)
146147
}
147148

cmd/postgres_exporter/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ func (s *Servers) GetServer(dsn string) (*Server, error) {
178178
s.servers[dsn] = server
179179
}
180180
if err = server.Ping(); err != nil {
181+
server.Close()
181182
delete(s.servers, dsn)
182183
time.Sleep(time.Duration(errCount) * time.Second)
183184
continue

collector/collector.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ func (p PostgresCollector) Collect(ch chan<- prometheus.Metric) {
164164

165165
// Set up the database connection for the collector.
166166
err := inst.setup()
167+
defer inst.Close()
167168
if err != nil {
168169
level.Error(p.logger).Log("msg", "Error opening connection to database", "err", err)
169170
return
170171
}
171-
defer inst.Close()
172172

173173
wg := sync.WaitGroup{}
174174
wg.Add(len(p.Collectors))
@@ -181,6 +181,10 @@ func (p PostgresCollector) Collect(ch chan<- prometheus.Metric) {
181181
wg.Wait()
182182
}
183183

184+
func (p *PostgresCollector) Close() error {
185+
return p.instance.Close()
186+
}
187+
184188
func execute(ctx context.Context, name string, c Collector, instance *instance, ch chan<- prometheus.Metric, logger log.Logger) {
185189
begin := time.Now()
186190
err := c.Update(ctx, instance, ch)

collector/pg_database.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,21 @@ var (
5353
"Disk space used by the database",
5454
[]string{"datname"}, nil,
5555
)
56+
pgDatabaseConnectionLimitsDesc = prometheus.NewDesc(
57+
prometheus.BuildFQName(
58+
namespace,
59+
databaseSubsystem,
60+
"connection_limit",
61+
),
62+
"Connection limit set for the database",
63+
[]string{"datname"}, nil,
64+
)
5665

57-
pgDatabaseQuery = "SELECT pg_database.datname FROM pg_database;"
66+
pgDatabaseQuery = "SELECT pg_database.datname, pg_database.datconnlimit FROM pg_database;"
5867
pgDatabaseSizeQuery = "SELECT pg_database_size($1)"
5968
)
6069

61-
// Update implements Collector and exposes database size.
70+
// Update implements Collector and exposes database size and connection limits.
6271
// It is called by the Prometheus registry when collecting metrics.
6372
// The list of databases is retrieved from pg_database and filtered
6473
// by the excludeDatabase config parameter. The tradeoff here is that
@@ -81,21 +90,32 @@ func (c PGDatabaseCollector) Update(ctx context.Context, instance *instance, ch
8190

8291
for rows.Next() {
8392
var datname sql.NullString
84-
if err := rows.Scan(&datname); err != nil {
93+
var connLimit sql.NullInt64
94+
if err := rows.Scan(&datname, &connLimit); err != nil {
8595
return err
8696
}
8797

8898
if !datname.Valid {
8999
continue
90100
}
101+
database := datname.String
91102
// Ignore excluded databases
92103
// Filtering is done here instead of in the query to avoid
93104
// a complicated NOT IN query with a variable number of parameters
94-
if sliceContains(c.excludedDatabases, datname.String) {
105+
if sliceContains(c.excludedDatabases, database) {
95106
continue
96107
}
97108

98-
databases = append(databases, datname.String)
109+
databases = append(databases, database)
110+
111+
connLimitMetric := 0.0
112+
if connLimit.Valid {
113+
connLimitMetric = float64(connLimit.Int64)
114+
}
115+
ch <- prometheus.MustNewConstMetric(
116+
pgDatabaseConnectionLimitsDesc,
117+
prometheus.GaugeValue, connLimitMetric, database,
118+
)
99119
}
100120

101121
// Query the size of the databases
@@ -114,6 +134,7 @@ func (c PGDatabaseCollector) Update(ctx context.Context, instance *instance, ch
114134
pgDatabaseSizeDesc,
115135
prometheus.GaugeValue, sizeMetric, datname,
116136
)
137+
117138
}
118139
return rows.Err()
119140
}

collector/pg_database_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func TestPGDatabaseCollector(t *testing.T) {
3131

3232
inst := &instance{db: db}
3333

34-
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname"}).
35-
AddRow("postgres"))
34+
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname", "datconnlimit"}).
35+
AddRow("postgres", 15))
3636

3737
mock.ExpectQuery(sanitizeQuery(pgDatabaseSizeQuery)).WithArgs("postgres").WillReturnRows(sqlmock.NewRows([]string{"pg_database_size"}).
3838
AddRow(1024))
@@ -47,6 +47,7 @@ func TestPGDatabaseCollector(t *testing.T) {
4747
}()
4848

4949
expected := []MetricResult{
50+
{labels: labelMap{"datname": "postgres"}, value: 15, metricType: dto.MetricType_GAUGE},
5051
{labels: labelMap{"datname": "postgres"}, value: 1024, metricType: dto.MetricType_GAUGE},
5152
}
5253
convey.Convey("Metrics comparison", t, func() {
@@ -71,8 +72,8 @@ func TestPGDatabaseCollectorNullMetric(t *testing.T) {
7172

7273
inst := &instance{db: db}
7374

74-
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname"}).
75-
AddRow("postgres"))
75+
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname", "datconnlimit"}).
76+
AddRow("postgres", nil))
7677

7778
mock.ExpectQuery(sanitizeQuery(pgDatabaseSizeQuery)).WithArgs("postgres").WillReturnRows(sqlmock.NewRows([]string{"pg_database_size"}).
7879
AddRow(nil))
@@ -88,6 +89,7 @@ func TestPGDatabaseCollectorNullMetric(t *testing.T) {
8889

8990
expected := []MetricResult{
9091
{labels: labelMap{"datname": "postgres"}, value: 0, metricType: dto.MetricType_GAUGE},
92+
{labels: labelMap{"datname": "postgres"}, value: 0, metricType: dto.MetricType_GAUGE},
9193
}
9294
convey.Convey("Metrics comparison", t, func() {
9395
for _, expect := range expected {

collector/pg_replication_slot.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ var (
4343
"slot_current_wal_lsn",
4444
),
4545
"current wal lsn value",
46-
[]string{"slot_name"}, nil,
46+
[]string{"slot_name", "slot_type"}, nil,
4747
)
4848
pgReplicationSlotCurrentFlushDesc = prometheus.NewDesc(
4949
prometheus.BuildFQName(
@@ -52,7 +52,7 @@ var (
5252
"slot_confirmed_flush_lsn",
5353
),
5454
"last lsn confirmed flushed to the replication slot",
55-
[]string{"slot_name"}, nil,
55+
[]string{"slot_name", "slot_type"}, nil,
5656
)
5757
pgReplicationSlotIsActiveDesc = prometheus.NewDesc(
5858
prometheus.BuildFQName(
@@ -61,18 +61,39 @@ var (
6161
"slot_is_active",
6262
),
6363
"whether the replication slot is active or not",
64-
[]string{"slot_name"}, nil,
64+
[]string{"slot_name", "slot_type"}, nil,
65+
)
66+
pgReplicationSlotSafeWal = prometheus.NewDesc(
67+
prometheus.BuildFQName(
68+
namespace,
69+
replicationSlotSubsystem,
70+
"safe_wal_size_bytes",
71+
),
72+
"number of bytes that can be written to WAL such that this slot is not in danger of getting in state lost",
73+
[]string{"slot_name", "slot_type"}, nil,
74+
)
75+
pgReplicationSlotWalStatus = prometheus.NewDesc(
76+
prometheus.BuildFQName(
77+
namespace,
78+
replicationSlotSubsystem,
79+
"wal_status",
80+
),
81+
"availability of WAL files claimed by this slot",
82+
[]string{"slot_name", "slot_type", "wal_status"}, nil,
6583
)
6684

6785
pgReplicationSlotQuery = `SELECT
6886
slot_name,
69-
CASE WHEN pg_is_in_recovery() THEN
87+
slot_type,
88+
CASE WHEN pg_is_in_recovery() THEN
7089
pg_last_wal_receive_lsn() - '0/0'
71-
ELSE
72-
pg_current_wal_lsn() - '0/0'
90+
ELSE
91+
pg_current_wal_lsn() - '0/0'
7392
END AS current_wal_lsn,
74-
COALESCE(confirmed_flush_lsn, '0/0') - '0/0',
75-
active
93+
COALESCE(confirmed_flush_lsn, '0/0') - '0/0' AS confirmed_flush_lsn,
94+
active,
95+
safe_wal_size,
96+
wal_status
7697
FROM pg_replication_slots;`
7798
)
7899

@@ -87,10 +108,13 @@ func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance
87108

88109
for rows.Next() {
89110
var slotName sql.NullString
111+
var slotType sql.NullString
90112
var walLSN sql.NullFloat64
91113
var flushLSN sql.NullFloat64
92114
var isActive sql.NullBool
93-
if err := rows.Scan(&slotName, &walLSN, &flushLSN, &isActive); err != nil {
115+
var safeWalSize sql.NullInt64
116+
var walStatus sql.NullString
117+
if err := rows.Scan(&slotName, &slotType, &walLSN, &flushLSN, &isActive, &safeWalSize, &walStatus); err != nil {
94118
return err
95119
}
96120

@@ -102,14 +126,18 @@ func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance
102126
if slotName.Valid {
103127
slotNameLabel = slotName.String
104128
}
129+
slotTypeLabel := "unknown"
130+
if slotType.Valid {
131+
slotTypeLabel = slotType.String
132+
}
105133

106134
var walLSNMetric float64
107135
if walLSN.Valid {
108136
walLSNMetric = walLSN.Float64
109137
}
110138
ch <- prometheus.MustNewConstMetric(
111139
pgReplicationSlotCurrentWalDesc,
112-
prometheus.GaugeValue, walLSNMetric, slotNameLabel,
140+
prometheus.GaugeValue, walLSNMetric, slotNameLabel, slotTypeLabel,
113141
)
114142
if isActive.Valid && isActive.Bool {
115143
var flushLSNMetric float64
@@ -118,13 +146,27 @@ func (PGReplicationSlotCollector) Update(ctx context.Context, instance *instance
118146
}
119147
ch <- prometheus.MustNewConstMetric(
120148
pgReplicationSlotCurrentFlushDesc,
121-
prometheus.GaugeValue, flushLSNMetric, slotNameLabel,
149+
prometheus.GaugeValue, flushLSNMetric, slotNameLabel, slotTypeLabel,
122150
)
123151
}
124152
ch <- prometheus.MustNewConstMetric(
125153
pgReplicationSlotIsActiveDesc,
126-
prometheus.GaugeValue, isActiveValue, slotNameLabel,
154+
prometheus.GaugeValue, isActiveValue, slotNameLabel, slotTypeLabel,
127155
)
156+
157+
if safeWalSize.Valid {
158+
ch <- prometheus.MustNewConstMetric(
159+
pgReplicationSlotSafeWal,
160+
prometheus.GaugeValue, float64(safeWalSize.Int64), slotNameLabel, slotTypeLabel,
161+
)
162+
}
163+
164+
if walStatus.Valid {
165+
ch <- prometheus.MustNewConstMetric(
166+
pgReplicationSlotWalStatus,
167+
prometheus.GaugeValue, 1, slotNameLabel, slotTypeLabel, walStatus.String,
168+
)
169+
}
128170
}
129171
return rows.Err()
130172
}

0 commit comments

Comments
 (0)