Skip to content

Commit

Permalink
v0.3.0
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Vonng committed Oct 29, 2020
1 parent 31852b4 commit 3296167
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ pg_exporter_test.go
test/
deploy/
upload.sh

temp/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ curl:
upload:
./upload.sh

release: clean conf release-linux release-darwin release-windows
release: clean conf release-linux release-darwin # release-windows


.PHONY: build clean release-linux release-darwin release-windows rpm release linux docker run curl conf upload
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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.

Latest `pg_exporter` version: `0.2.0`
Latest `pg_exporter` version: `0.3.0`



Expand Down
85 changes: 68 additions & 17 deletions pg_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// ┃ Desc : pg_exporter metrics exporter ┃ //
// ┃ Ctime : 2019-12-09 ┃ //
// ┃ Mtime : 2020-10-20 ┃ //
// ┃ Version : 0.2.0 ┃ //
// ┃ Version : 0.3.0 ┃ //
// ┃ Support : PostgreSQL 10~13 pgbouncer 1.9+ ┃ //
// ┃ Author : Vonng (fengruohang@outlook.com) ┃ //
// ┃ Copyright (C) 2019-2020 Ruohang Feng ┃ //
Expand All @@ -16,10 +16,9 @@ package main
import (
"bytes"
"context"
"database/sql"
"fmt"
"github.com/lib/pq"
"io/ioutil"
"database/sql"
"math"
"net/http"
"net/url"
Expand All @@ -36,6 +35,7 @@ import (
"time"

_ "github.com/lib/pq"
"github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
Expand All @@ -48,7 +48,7 @@ import (
\**********************************************************************************************/

// Version is read by make build procedure
var Version = "0.2.0"
var Version = "0.3.0"

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

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

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

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

// Status will report 3 available status: primary|replica|down
// Status will report 4 available status: primary|replica|down|unknown
func (e *Exporter) Status() string {
if e.server == nil || e.scrapeDone.IsZero() {
if e.server == nil {
return `unknown`
}
if !e.server.UP {
Expand Down Expand Up @@ -1309,6 +1314,15 @@ func (e *Exporter) Explain() string {
return strings.Join(e.server.Explain(), "\n\n")
}

// Check will perform an immediate server health check
func (e *Exporter) Check() {
if err := e.server.Check(); err != nil {
log.Errorf("exporter check failure: %s", err.Error())
} else {
log.Debugf("exporter check ok")
}
}

// Close will close all underlying servers
func (e *Exporter) Close() {
if e.server != nil {
Expand Down Expand Up @@ -1601,6 +1615,7 @@ func (e *Exporter) ExplainFunc(w http.ResponseWriter, r *http.Request) {
// UpCheckFunc tells whether target instance is alive, 200 up 503 down
func (e *Exporter) UpCheckFunc(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
e.Check()
if e.Up() {
w.WriteHeader(200)
_, _ = w.Write([]byte(PgExporter.Status()))
Expand All @@ -1613,6 +1628,7 @@ func (e *Exporter) UpCheckFunc(w http.ResponseWriter, r *http.Request) {
// PrimaryCheckFunc tells whether target instance is a primary, 200 yes 404 no 503 unknown
func (e *Exporter) PrimaryCheckFunc(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
e.Check()
if PgExporter.Up() {
if PgExporter.Recovery() {
w.WriteHeader(404)
Expand All @@ -1630,6 +1646,7 @@ func (e *Exporter) PrimaryCheckFunc(w http.ResponseWriter, r *http.Request) {
// ReplicaCheckFunc tells whether target instance is a replica, 200 yes 404 no 503 unknown
func (e *Exporter) ReplicaCheckFunc(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
e.Check()
if PgExporter.Up() {
if PgExporter.Recovery() {
w.WriteHeader(200)
Expand Down Expand Up @@ -1791,11 +1808,7 @@ func shadowDSN(dsn string) string {
if err != nil {
return ""
}
// Blank user info if not nil
if pDSN.User != nil {
pDSN.User = url.UserPassword(pDSN.User.Username(), "PASSWORD")
}
return pDSN.String()
return pDSN.Redacted()
}

// parseDatname extract datname part of a dsn
Expand Down Expand Up @@ -1833,9 +1846,41 @@ func RetrieveTargetURL() (res string) {
}
}
log.Warnf("fail retrieving target url, fallback on default url: %s", defaultPGURL)

// process URL (add missing sslmode)
return defaultPGURL
}

// ProcessURL will fix URL with default options
func ProcessURL(pgUrlStr string) string {
u, err := url.Parse(pgUrlStr)
if err != nil {
log.Errorf("invalid url format %s", pgUrlStr)
return ""
}

// add sslmode = disable if not exists
qs := u.Query()
if sslmode := qs.Get(`sslmode`); sslmode == "" {
qs.Set(`sslmode`, `disable`)
}
var buf strings.Builder
for k, v := range qs {
if len(v) == 0 {
continue
}
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v[0])
}
u.RawQuery = buf.String()
fmt.Println(u.String())
return u.String()
}

// RetrieveConfig config path
func RetrieveConfig() (res string) {
// priority: cli-args > env > default settings (check exist)
Expand Down Expand Up @@ -1913,6 +1958,7 @@ func Reload() error {
if PgExporter != nil {
// DO NOT MANUALLY CLOSE OLD EXPORTER INSTANCE because the stupid implementation of sql.DB
// there connection will be automatically released after 1 min
// PgExporter.Close()
prometheus.Unregister(PgExporter)
}
PgExporter = newExporter
Expand All @@ -1927,15 +1973,21 @@ func ParseArgs() {
kingpin.Parse()
log.Debugf("init pg_exporter, configPath=%v constLabels=%v, disableCache=%v, autoDiscovery=%v, excludeDatabase=%v listenAdress=%v metricPath=%v",
*configPath, *constLabels, *disableCache, *autoDiscovery, *excludeDatabase, *listenAddress, *metricPath)
*pgURL = RetrieveTargetURL()
*pgURL = ProcessURL(RetrieveTargetURL())
*configPath = RetrieveConfig()
}

// DummyServer reponse with a dummy metrics pg_up 0 or pgbouncer_up 0
func DummyServer() (s *http.Server, exit <-chan bool) {
mux := http.NewServeMux()
dummyMetricName := `pg_up`
if parseDatname(*pgURL) == `pgbouncer` {
dummyMetricName = `pgbouncer_up`
}
mux.HandleFunc(*metricPath, func(w http.ResponseWriter, req *http.Request) {
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")
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)
})

httpServer := &http.Server{
Addr: *listenAddress,
Handler: mux,
Expand Down Expand Up @@ -2011,7 +2063,6 @@ func Run() {
}
}()


/*************** REST API ***************/
// basic
http.HandleFunc("/", TitleFunc)
Expand Down Expand Up @@ -2040,8 +2091,8 @@ func Run() {
http.HandleFunc("/ro", PgExporter.ReplicaCheckFunc)

// metric
dummySrv.Close()
<- closeChan
_ = dummySrv.Close()
<-closeChan
http.Handle(*metricPath, promhttp.Handler())

log.Infof("pg_exporter for %s start, listen on http://%s%s", shadowDSN(*pgURL), *listenAddress, *metricPath)
Expand Down
3 changes: 3 additions & 0 deletions service/pg_exporter.service
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ User=prometheus
ExecStart=/usr/bin/pg_exporter $PG_EXPORTER_OPTS
Restart=on-failure

CPUQuota=25%
MemoryMax=256M

[Install]
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion service/pg_exporter.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
%define debug_package %{nil}

Name: pg_exporter
Version: 0.2.0
Version: 0.3.0
Release: 1%{?dist}
Summary: Prometheus exporter for PostgreSQL/Pgbouncer server metrics
License: BSD
Expand Down

0 comments on commit 3296167

Please sign in to comment.