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
1616import (
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
5353var defaultPGURL = "postgresql:///?sslmode=disable"
5454
@@ -751,6 +751,7 @@ func PgbouncerPrecheck(s *Server) (err error) {
751751func 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
12171222func (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
13131327func (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
16021616func (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
16141629func (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
16311647func (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
18401885func 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
19341981func 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\n pg_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 )
0 commit comments