mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-16 20:15:32 +00:00
Refactor exporter so that it can be used as a library.
This commit is contained in:
parent
4117fb2afc
commit
2ab8f10935
2
Makefile
2
Makefile
@ -89,7 +89,7 @@ fmt: tools
|
||||
postgres_exporter_integration_test: $(GO_SRC)
|
||||
CGO_ENABLED=0 go test -c -tags integration \
|
||||
-a -ldflags "-extldflags '-static' -X main.Version=$(VERSION)" \
|
||||
-o postgres_exporter_integration_test -cover -covermode count .
|
||||
-o postgres_exporter_integration_test -cover -covermode count ./collector/...
|
||||
|
||||
test: tools
|
||||
@mkdir -p $(COVERDIR)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package collector
|
||||
|
||||
import (
|
||||
"database/sql"
|
@ -1,6 +1,6 @@
|
||||
// +build !integration
|
||||
|
||||
package main
|
||||
package collector
|
||||
|
||||
import (
|
||||
dto "github.com/prometheus/client_model/go"
|
@ -1,41 +1,25 @@
|
||||
package main
|
||||
package collector
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"crypto/sha256"
|
||||
"github.com/blang/semver"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/log"
|
||||
)
|
||||
|
||||
// Version is set during build to the git describe version
|
||||
// (semantic version)-(commitish) form.
|
||||
var Version = "0.0.1"
|
||||
|
||||
var (
|
||||
listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9187").OverrideDefaultFromEnvar("PG_EXPORTER_WEB_LISTEN_ADDRESS").String()
|
||||
metricPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").OverrideDefaultFromEnvar("PG_EXPORTER_WEB_TELEMETRY_PATH").String()
|
||||
queriesPath = kingpin.Flag("extend.query-path", "Path to custom queries to run.").Default("").OverrideDefaultFromEnvar("PG_EXPORTER_EXTEND_QUERY_PATH").String()
|
||||
onlyDumpMaps = kingpin.Flag("dumpmaps", "Do not run, simply dump the maps.").Bool()
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Metric name parts.
|
||||
@ -124,7 +108,7 @@ type MetricMap struct {
|
||||
}
|
||||
|
||||
// TODO: revisit this with the semver system
|
||||
func dumpMaps() {
|
||||
func DumpMaps() {
|
||||
// TODO: make this function part of the exporter
|
||||
for name, cmap := range builtinMetricMaps {
|
||||
query, ok := queryOverrides[name]
|
||||
@ -727,6 +711,12 @@ func NewExporter(dsn string, userQueriesPath string) *Exporter {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Exporter) Close() {
|
||||
if e.dbConnection != nil {
|
||||
e.dbConnection.Close() // nolint: errcheck
|
||||
}
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
// We cannot know in advance what metrics the exporter will generate
|
||||
@ -969,12 +959,15 @@ func (e *Exporter) getDB(conn string) (*sql.DB, error) {
|
||||
if e.dbConnection == nil {
|
||||
d, err := sql.Open("postgres", conn)
|
||||
if err != nil {
|
||||
e.psqlUp.Set(0)
|
||||
return nil, err
|
||||
}
|
||||
err = d.Ping()
|
||||
if err != nil {
|
||||
e.psqlUp.Set(0)
|
||||
return nil, err
|
||||
}
|
||||
e.psqlUp.Set(1)
|
||||
|
||||
d.SetMaxOpenConns(1)
|
||||
d.SetMaxIdleConns(1)
|
||||
@ -1008,7 +1001,6 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
log.Infof("Error opening connection to database (%s): %s", loggableDsn, err)
|
||||
e.error.Set(1)
|
||||
e.psqlUp.Set(0) // Force "up" to 0 here.
|
||||
return
|
||||
}
|
||||
|
||||
@ -1036,7 +1028,7 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
|
||||
// DATA_SOURCE_NAME always wins so we do not break older versions
|
||||
// reading secrets from files wins over secrets in environment variables
|
||||
// DATA_SOURCE_NAME > DATA_SOURCE_{USER|FILE}_FILE > DATA_SOURCE_{USER|FILE}
|
||||
func getDataSource() string {
|
||||
func GetDataSource() string {
|
||||
var dsn = os.Getenv("DATA_SOURCE_NAME")
|
||||
if len(dsn) == 0 {
|
||||
var user string
|
||||
@ -1068,48 +1060,3 @@ func getDataSource() string {
|
||||
|
||||
return dsn
|
||||
}
|
||||
|
||||
func main() {
|
||||
kingpin.Version(fmt.Sprintf("postgres_exporter %s (built with %s)\n", Version, runtime.Version()))
|
||||
log.AddFlags(kingpin.CommandLine)
|
||||
kingpin.Parse()
|
||||
|
||||
// landingPage contains the HTML served at '/'.
|
||||
// TODO: Make this nicer and more informative.
|
||||
var landingPage = []byte(`<html>
|
||||
<head><title>Postgres exporter</title></head>
|
||||
<body>
|
||||
<h1>Postgres exporter</h1>
|
||||
<p><a href='` + *metricPath + `'>Metrics</a></p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
if *onlyDumpMaps {
|
||||
dumpMaps()
|
||||
return
|
||||
}
|
||||
|
||||
dsn := getDataSource()
|
||||
if len(dsn) == 0 {
|
||||
log.Fatal("couldn't find environment variables describing the datasource to use")
|
||||
}
|
||||
|
||||
exporter := NewExporter(dsn, *queriesPath)
|
||||
defer func() {
|
||||
if exporter.dbConnection != nil {
|
||||
exporter.dbConnection.Close() // nolint: errcheck
|
||||
}
|
||||
}()
|
||||
|
||||
prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(*metricPath, promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "Content-Type:text/plain; charset=UTF-8") // nolint: errcheck
|
||||
w.Write(landingPage) // nolint: errcheck
|
||||
})
|
||||
|
||||
log.Infof("Starting Server: %s", *listenAddress)
|
||||
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
||||
}
|
@ -3,19 +3,17 @@
|
||||
// working.
|
||||
// +build integration
|
||||
|
||||
package main
|
||||
package collector
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Hook up gocheck into the "go test" runner.
|
||||
@ -78,6 +76,8 @@ func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) {
|
||||
// the exporter. Related to https://github.com/wrouesnel/postgres_exporter/issues/93
|
||||
// although not a replication of the scenario.
|
||||
func (s *IntegrationSuite) TestInvalidDsnDoesntCrash(c *C) {
|
||||
queriesPath := os.Getenv("PG_EXPORTER_EXTEND_QUERY_PATH")
|
||||
|
||||
// Setup a dummy channel to consume metrics
|
||||
ch := make(chan prometheus.Metric, 100)
|
||||
go func() {
|
||||
@ -86,12 +86,12 @@ func (s *IntegrationSuite) TestInvalidDsnDoesntCrash(c *C) {
|
||||
}()
|
||||
|
||||
// Send a bad DSN
|
||||
exporter := NewExporter("invalid dsn", *queriesPath)
|
||||
exporter := NewExporter("invalid dsn", queriesPath)
|
||||
c.Assert(exporter, NotNil)
|
||||
exporter.scrape(ch)
|
||||
|
||||
// Send a DSN to a non-listening port.
|
||||
exporter = NewExporter("postgresql://nothing:nothing@127.0.0.1:1/nothing", *queriesPath)
|
||||
exporter = NewExporter("postgresql://nothing:nothing@127.0.0.1:1/nothing", queriesPath)
|
||||
c.Assert(exporter, NotNil)
|
||||
exporter.scrape(ch)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// +build !integration
|
||||
|
||||
package main
|
||||
package collector
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
@ -89,11 +89,11 @@ func (s *FunctionalSuite) TestSemanticVersionColumnDiscard(c *C) {
|
||||
// test read username and password from file
|
||||
func (s *FunctionalSuite) TestEnvironmentSettingWithSecretsFiles(c *C) {
|
||||
|
||||
err := os.Setenv("DATA_SOURCE_USER_FILE", "./tests/username_file")
|
||||
err := os.Setenv("DATA_SOURCE_USER_FILE", "../tests/username_file")
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_USER_FILE")
|
||||
|
||||
err = os.Setenv("DATA_SOURCE_PASS_FILE", "./tests/userpass_file")
|
||||
err = os.Setenv("DATA_SOURCE_PASS_FILE", "../tests/userpass_file")
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_PASS_FILE")
|
||||
|
||||
@ -103,7 +103,7 @@ func (s *FunctionalSuite) TestEnvironmentSettingWithSecretsFiles(c *C) {
|
||||
|
||||
var expected = "postgresql://custom_username:custom_password@localhost:5432/?sslmode=disable"
|
||||
|
||||
dsn := getDataSource()
|
||||
dsn := GetDataSource()
|
||||
if dsn != expected {
|
||||
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, expected)
|
||||
}
|
||||
@ -117,7 +117,7 @@ func (s *FunctionalSuite) TestEnvironmentSettingWithDns(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_NAME")
|
||||
|
||||
dsn := getDataSource()
|
||||
dsn := GetDataSource()
|
||||
if dsn != envDsn {
|
||||
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, envDsn)
|
||||
}
|
||||
@ -131,7 +131,7 @@ func (s *FunctionalSuite) TestEnvironmentSettingWithDnsAndSecrets(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_NAME")
|
||||
|
||||
err = os.Setenv("DATA_SOURCE_USER_FILE", "./tests/username_file")
|
||||
err = os.Setenv("DATA_SOURCE_USER_FILE", "../tests/username_file")
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_USER_FILE")
|
||||
|
||||
@ -139,7 +139,7 @@ func (s *FunctionalSuite) TestEnvironmentSettingWithDnsAndSecrets(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
defer UnsetEnvironment(c, "DATA_SOURCE_PASS")
|
||||
|
||||
dsn := getDataSource()
|
||||
dsn := GetDataSource()
|
||||
if dsn != envDsn {
|
||||
c.Errorf("Expected Username to be read from file. Found=%v, expected=%v", dsn, envDsn)
|
||||
}
|
66
main.go
Normal file
66
main.go
Normal file
@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/wrouesnel/postgres_exporter/collector"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
// Version is set during build to the git describe version
|
||||
// (semantic version)-(commitish) form.
|
||||
var Version = "0.0.1"
|
||||
|
||||
var (
|
||||
listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9187").OverrideDefaultFromEnvar("PG_EXPORTER_WEB_LISTEN_ADDRESS").String()
|
||||
metricPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").OverrideDefaultFromEnvar("PG_EXPORTER_WEB_TELEMETRY_PATH").String()
|
||||
queriesPath = kingpin.Flag("extend.query-path", "Path to custom queries to run.").Default("").OverrideDefaultFromEnvar("PG_EXPORTER_EXTEND_QUERY_PATH").String()
|
||||
onlyDumpMaps = kingpin.Flag("dumpmaps", "Do not run, simply dump the maps.").Bool()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Version(fmt.Sprintf("postgres_exporter %s (built with %s)\n", Version, runtime.Version()))
|
||||
log.AddFlags(kingpin.CommandLine)
|
||||
kingpin.Parse()
|
||||
|
||||
// landingPage contains the HTML served at '/'.
|
||||
// TODO: Make this nicer and more informative.
|
||||
var landingPage = []byte(`<html>
|
||||
<head><title>Postgres exporter</title></head>
|
||||
<body>
|
||||
<h1>Postgres exporter</h1>
|
||||
<p><a href='` + *metricPath + `'>Metrics</a></p>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
if *onlyDumpMaps {
|
||||
collector.DumpMaps()
|
||||
return
|
||||
}
|
||||
|
||||
dsn := collector.GetDataSource()
|
||||
if len(dsn) == 0 {
|
||||
log.Fatal("couldn't find environment variables describing the datasource to use")
|
||||
}
|
||||
|
||||
exporter := collector.NewExporter(dsn, *queriesPath)
|
||||
defer exporter.Close()
|
||||
|
||||
prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(*metricPath, promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "Content-Type:text/plain; charset=UTF-8") // nolint: errcheck
|
||||
w.Write(landingPage) // nolint: errcheck
|
||||
})
|
||||
|
||||
log.Infof("Starting Server: %s", *listenAddress)
|
||||
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
||||
}
|
Loading…
Reference in New Issue
Block a user