Skip to content

Commit 3296167

Browse files
committed
v0.3.0
* upgrade: default configurations to be compatible with postgresql13 * feature: add health check endpoint * feature: add dummy metrics pg_up and pgbouncer_up * fix small bugs
1 parent 31852b4 commit 3296167

File tree

6 files changed

+76
-20
lines changed

6 files changed

+76
-20
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ pg_exporter_test.go
2424
test/
2525
deploy/
2626
upload.sh
27+
28+
temp/

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ curl:
6161
upload:
6262
./upload.sh
6363

64-
release: clean conf release-linux release-darwin release-windows
64+
release: clean conf release-linux release-darwin # release-windows
6565

6666

6767
.PHONY: build clean release-linux release-darwin release-windows rpm release linux docker run curl conf upload

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Latest binaries & rpms can be found on [release](https://github.com/Vonng/pg_exporter/releases) page. Supported pg version: PostgreSQL 9.4+ & Pgbouncer 1.8+. Default collectors definition is compatible with PostgreSQL 10,11,12,13.
66

7-
Latest `pg_exporter` version: `0.2.0`
7+
Latest `pg_exporter` version: `0.3.0`
88

99

1010

pg_exporter.go

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// ┃ Desc : pg_exporter metrics exporter ┃ //
66
// ┃ Ctime : 2019-12-09 ┃ //
77
// ┃ Mtime : 2020-10-20 ┃ //
8-
// ┃ Version : 0.2.0 ┃ //
8+
// ┃ Version : 0.3.0 ┃ //
99
// ┃ Support : PostgreSQL 10~13 pgbouncer 1.9+ ┃ //
1010
// ┃ Author : Vonng (fengruohang@outlook.com) ┃ //
1111
// ┃ Copyright (C) 2019-2020 Ruohang Feng ┃ //
@@ -16,10 +16,9 @@ package main
1616
import (
1717
"bytes"
1818
"context"
19-
"database/sql"
2019
"fmt"
21-
"github.com/lib/pq"
2220
"io/ioutil"
21+
"database/sql"
2322
"math"
2423
"net/http"
2524
"net/url"
@@ -36,6 +35,7 @@ import (
3635
"time"
3736

3837
_ "github.com/lib/pq"
38+
"github.com/lib/pq"
3939
"github.com/prometheus/client_golang/prometheus"
4040
"github.com/prometheus/client_golang/prometheus/promhttp"
4141
"github.com/prometheus/common/log"
@@ -48,7 +48,7 @@ import (
4848
\**********************************************************************************************/
4949

5050
// Version is read by make build procedure
51-
var Version = "0.2.0"
51+
var Version = "0.3.0"
5252

5353
var defaultPGURL = "postgresql:///?sslmode=disable"
5454

@@ -751,6 +751,7 @@ func PgbouncerPrecheck(s *Server) (err error) {
751751
func PostgresPrecheck(s *Server) (err error) {
752752
if s.DB == nil { // if db is not initialized, create a new DB
753753
if s.DB, err = sql.Open("postgres", s.dsn); err != nil {
754+
s.UP = false
754755
return
755756
}
756757
s.DB.SetMaxIdleConns(1)
@@ -763,8 +764,10 @@ func PostgresPrecheck(s *Server) (err error) {
763764
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
764765
defer cancel()
765766
if err = s.DB.QueryRowContext(ctx, `SHOW server_version_num;`).Scan(&version); err != nil {
767+
s.UP = false
766768
return fmt.Errorf("fail fetching server version: %w", err)
767769
}
770+
s.UP = true
768771
// fact change triggers a new planning
769772
if s.Version != version {
770773
log.Infof("server [%s] version changed: from [%d] to [%d]", s.Name(), s.Version, version)
@@ -774,6 +777,7 @@ func PostgresPrecheck(s *Server) (err error) {
774777

775778
// do not check here
776779
if _, err = s.DB.Exec(`SET application_name = pg_exporter;`); err != nil {
780+
s.UP = false
777781
return fmt.Errorf("fail settting application name: %w", err)
778782
}
779783

@@ -789,6 +793,7 @@ func PostgresPrecheck(s *Server) (err error) {
789793
defer cancel2()
790794
if err = s.DB.QueryRowContext(ctx, precheckSQL).Scan(&datname, &username, &recovery, pq.Array(&databases), pq.Array(&namespaces), pq.Array(&extensions));
791795
err != nil {
796+
s.UP = false
792797
return fmt.Errorf("fail fetching server version: %w", err)
793798
}
794799
if s.Recovery != recovery {
@@ -828,7 +833,7 @@ func PostgresPrecheck(s *Server) (err error) {
828833
}
829834
}
830835
// if old db is not found in new db list, add a change entry [OldDBName:false]
831-
for dbname, _ := range s.Databases {
836+
for dbname := range s.Databases {
832837
if _, found := newDBList[dbname]; !found {
833838
log.Debugf("server [%s] found vanished database %s", s.Name(), dbname)
834839
changes[dbname] = false
@@ -1213,9 +1218,9 @@ func (e *Exporter) Recovery() bool {
12131218
return e.server.Recovery
12141219
}
12151220

1216-
// Status will report 3 available status: primary|replica|down
1221+
// Status will report 4 available status: primary|replica|down|unknown
12171222
func (e *Exporter) Status() string {
1218-
if e.server == nil || e.scrapeDone.IsZero() {
1223+
if e.server == nil {
12191224
return `unknown`
12201225
}
12211226
if !e.server.UP {
@@ -1309,6 +1314,15 @@ func (e *Exporter) Explain() string {
13091314
return strings.Join(e.server.Explain(), "\n\n")
13101315
}
13111316

1317+
// Check will perform an immediate server health check
1318+
func (e *Exporter) Check() {
1319+
if err := e.server.Check(); err != nil {
1320+
log.Errorf("exporter check failure: %s", err.Error())
1321+
} else {
1322+
log.Debugf("exporter check ok")
1323+
}
1324+
}
1325+
13121326
// Close will close all underlying servers
13131327
func (e *Exporter) Close() {
13141328
if e.server != nil {
@@ -1601,6 +1615,7 @@ func (e *Exporter) ExplainFunc(w http.ResponseWriter, r *http.Request) {
16011615
// UpCheckFunc tells whether target instance is alive, 200 up 503 down
16021616
func (e *Exporter) UpCheckFunc(w http.ResponseWriter, r *http.Request) {
16031617
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
1618+
e.Check()
16041619
if e.Up() {
16051620
w.WriteHeader(200)
16061621
_, _ = w.Write([]byte(PgExporter.Status()))
@@ -1613,6 +1628,7 @@ func (e *Exporter) UpCheckFunc(w http.ResponseWriter, r *http.Request) {
16131628
// PrimaryCheckFunc tells whether target instance is a primary, 200 yes 404 no 503 unknown
16141629
func (e *Exporter) PrimaryCheckFunc(w http.ResponseWriter, r *http.Request) {
16151630
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
1631+
e.Check()
16161632
if PgExporter.Up() {
16171633
if PgExporter.Recovery() {
16181634
w.WriteHeader(404)
@@ -1630,6 +1646,7 @@ func (e *Exporter) PrimaryCheckFunc(w http.ResponseWriter, r *http.Request) {
16301646
// ReplicaCheckFunc tells whether target instance is a replica, 200 yes 404 no 503 unknown
16311647
func (e *Exporter) ReplicaCheckFunc(w http.ResponseWriter, r *http.Request) {
16321648
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
1649+
e.Check()
16331650
if PgExporter.Up() {
16341651
if PgExporter.Recovery() {
16351652
w.WriteHeader(200)
@@ -1791,11 +1808,7 @@ func shadowDSN(dsn string) string {
17911808
if err != nil {
17921809
return ""
17931810
}
1794-
// Blank user info if not nil
1795-
if pDSN.User != nil {
1796-
pDSN.User = url.UserPassword(pDSN.User.Username(), "PASSWORD")
1797-
}
1798-
return pDSN.String()
1811+
return pDSN.Redacted()
17991812
}
18001813

18011814
// parseDatname extract datname part of a dsn
@@ -1833,9 +1846,41 @@ func RetrieveTargetURL() (res string) {
18331846
}
18341847
}
18351848
log.Warnf("fail retrieving target url, fallback on default url: %s", defaultPGURL)
1849+
1850+
// process URL (add missing sslmode)
18361851
return defaultPGURL
18371852
}
18381853

1854+
// ProcessURL will fix URL with default options
1855+
func ProcessURL(pgUrlStr string) string {
1856+
u, err := url.Parse(pgUrlStr)
1857+
if err != nil {
1858+
log.Errorf("invalid url format %s", pgUrlStr)
1859+
return ""
1860+
}
1861+
1862+
// add sslmode = disable if not exists
1863+
qs := u.Query()
1864+
if sslmode := qs.Get(`sslmode`); sslmode == "" {
1865+
qs.Set(`sslmode`, `disable`)
1866+
}
1867+
var buf strings.Builder
1868+
for k, v := range qs {
1869+
if len(v) == 0 {
1870+
continue
1871+
}
1872+
if buf.Len() > 0 {
1873+
buf.WriteByte('&')
1874+
}
1875+
buf.WriteString(k)
1876+
buf.WriteByte('=')
1877+
buf.WriteString(v[0])
1878+
}
1879+
u.RawQuery = buf.String()
1880+
fmt.Println(u.String())
1881+
return u.String()
1882+
}
1883+
18391884
// RetrieveConfig config path
18401885
func RetrieveConfig() (res string) {
18411886
// priority: cli-args > env > default settings (check exist)
@@ -1913,6 +1958,7 @@ func Reload() error {
19131958
if PgExporter != nil {
19141959
// DO NOT MANUALLY CLOSE OLD EXPORTER INSTANCE because the stupid implementation of sql.DB
19151960
// there connection will be automatically released after 1 min
1961+
// PgExporter.Close()
19161962
prometheus.Unregister(PgExporter)
19171963
}
19181964
PgExporter = newExporter
@@ -1927,15 +1973,21 @@ func ParseArgs() {
19271973
kingpin.Parse()
19281974
log.Debugf("init pg_exporter, configPath=%v constLabels=%v, disableCache=%v, autoDiscovery=%v, excludeDatabase=%v listenAdress=%v metricPath=%v",
19291975
*configPath, *constLabels, *disableCache, *autoDiscovery, *excludeDatabase, *listenAddress, *metricPath)
1930-
*pgURL = RetrieveTargetURL()
1976+
*pgURL = ProcessURL(RetrieveTargetURL())
19311977
*configPath = RetrieveConfig()
19321978
}
19331979

1980+
// DummyServer reponse with a dummy metrics pg_up 0 or pgbouncer_up 0
19341981
func DummyServer() (s *http.Server, exit <-chan bool) {
19351982
mux := http.NewServeMux()
1983+
dummyMetricName := `pg_up`
1984+
if parseDatname(*pgURL) == `pgbouncer` {
1985+
dummyMetricName = `pgbouncer_up`
1986+
}
19361987
mux.HandleFunc(*metricPath, func(w http.ResponseWriter, req *http.Request) {
1937-
fmt.Fprintf(w, "# HELP pg_up last scrape was able to connect to the server: 1 for yes, 0 for no\n# TYPE pg_up gauge\npg_up 0")
1988+
fmt.Fprintf(w, "# HELP %s last scrape was able to connect to the server: 1 for yes, 0 for no\n# TYPE %s gauge\n%s 0", dummyMetricName, dummyMetricName, dummyMetricName)
19381989
})
1990+
19391991
httpServer := &http.Server{
19401992
Addr: *listenAddress,
19411993
Handler: mux,
@@ -2011,7 +2063,6 @@ func Run() {
20112063
}
20122064
}()
20132065

2014-
20152066
/*************** REST API ***************/
20162067
// basic
20172068
http.HandleFunc("/", TitleFunc)
@@ -2040,8 +2091,8 @@ func Run() {
20402091
http.HandleFunc("/ro", PgExporter.ReplicaCheckFunc)
20412092

20422093
// metric
2043-
dummySrv.Close()
2044-
<- closeChan
2094+
_ = dummySrv.Close()
2095+
<-closeChan
20452096
http.Handle(*metricPath, promhttp.Handler())
20462097

20472098
log.Infof("pg_exporter for %s start, listen on http://%s%s", shadowDSN(*pgURL), *listenAddress, *metricPath)

service/pg_exporter.service

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ User=prometheus
1111
ExecStart=/usr/bin/pg_exporter $PG_EXPORTER_OPTS
1212
Restart=on-failure
1313

14+
CPUQuota=25%
15+
MemoryMax=256M
16+
1417
[Install]
1518
WantedBy=multi-user.target

service/pg_exporter.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%define debug_package %{nil}
22

33
Name: pg_exporter
4-
Version: 0.2.0
4+
Version: 0.3.0
55
Release: 1%{?dist}
66
Summary: Prometheus exporter for PostgreSQL/Pgbouncer server metrics
77
License: BSD

0 commit comments

Comments
 (0)