Skip to content

Commit df461c2

Browse files
authored
Merge pull request #552 from sysadmind/refactor-files
Refactor code into logical files
2 parents 134e908 + bc981e6 commit df461c2

File tree

7 files changed

+1300
-1147
lines changed

7 files changed

+1300
-1147
lines changed

Diff for: cmd/postgres_exporter/datasource.go

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2021 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"fmt"
18+
"io/ioutil"
19+
"net/url"
20+
"os"
21+
"regexp"
22+
"strings"
23+
24+
"github.com/go-kit/kit/log/level"
25+
"github.com/prometheus/client_golang/prometheus"
26+
)
27+
28+
func (e *Exporter) discoverDatabaseDSNs() []string {
29+
// connstring syntax is complex (and not sure if even regular).
30+
// we don't need to parse it, so just superficially validate that it starts
31+
// with a valid-ish keyword pair
32+
connstringRe := regexp.MustCompile(`^ *[a-zA-Z0-9]+ *= *[^= ]+`)
33+
34+
dsns := make(map[string]struct{})
35+
for _, dsn := range e.dsn {
36+
var dsnURI *url.URL
37+
var dsnConnstring string
38+
39+
if strings.HasPrefix(dsn, "postgresql://") {
40+
var err error
41+
dsnURI, err = url.Parse(dsn)
42+
if err != nil {
43+
level.Error(logger).Log("msg", "Unable to parse DSN as URI", "dsn", loggableDSN(dsn), "err", err)
44+
continue
45+
}
46+
} else if connstringRe.MatchString(dsn) {
47+
dsnConnstring = dsn
48+
} else {
49+
level.Error(logger).Log("msg", "Unable to parse DSN as either URI or connstring", "dsn", loggableDSN(dsn))
50+
continue
51+
}
52+
53+
server, err := e.servers.GetServer(dsn)
54+
if err != nil {
55+
level.Error(logger).Log("msg", "Error opening connection to database", "dsn", loggableDSN(dsn), "err", err)
56+
continue
57+
}
58+
dsns[dsn] = struct{}{}
59+
60+
// If autoDiscoverDatabases is true, set first dsn as master database (Default: false)
61+
server.master = true
62+
63+
databaseNames, err := queryDatabases(server)
64+
if err != nil {
65+
level.Error(logger).Log("msg", "Error querying databases", "dsn", loggableDSN(dsn), "err", err)
66+
continue
67+
}
68+
for _, databaseName := range databaseNames {
69+
if contains(e.excludeDatabases, databaseName) {
70+
continue
71+
}
72+
73+
if len(e.includeDatabases) != 0 && !contains(e.includeDatabases, databaseName) {
74+
continue
75+
}
76+
77+
if dsnURI != nil {
78+
dsnURI.Path = databaseName
79+
dsn = dsnURI.String()
80+
} else {
81+
// replacing one dbname with another is complicated.
82+
// just append new dbname to override.
83+
dsn = fmt.Sprintf("%s dbname=%s", dsnConnstring, databaseName)
84+
}
85+
dsns[dsn] = struct{}{}
86+
}
87+
}
88+
89+
result := make([]string, len(dsns))
90+
index := 0
91+
for dsn := range dsns {
92+
result[index] = dsn
93+
index++
94+
}
95+
96+
return result
97+
}
98+
99+
func (e *Exporter) scrapeDSN(ch chan<- prometheus.Metric, dsn string) error {
100+
server, err := e.servers.GetServer(dsn)
101+
102+
if err != nil {
103+
return &ErrorConnectToServer{fmt.Sprintf("Error opening connection to database (%s): %s", loggableDSN(dsn), err.Error())}
104+
}
105+
106+
// Check if autoDiscoverDatabases is false, set dsn as master database (Default: false)
107+
if !e.autoDiscoverDatabases {
108+
server.master = true
109+
}
110+
111+
// Check if map versions need to be updated
112+
if err := e.checkMapVersions(ch, server); err != nil {
113+
level.Warn(logger).Log("msg", "Proceeding with outdated query maps, as the Postgres version could not be determined", "err", err)
114+
}
115+
116+
return server.Scrape(ch, e.disableSettingsMetrics)
117+
}
118+
119+
// try to get the DataSource
120+
// DATA_SOURCE_NAME always wins so we do not break older versions
121+
// reading secrets from files wins over secrets in environment variables
122+
// DATA_SOURCE_NAME > DATA_SOURCE_{USER|PASS}_FILE > DATA_SOURCE_{USER|PASS}
123+
func getDataSources() ([]string, error) {
124+
var dsn = os.Getenv("DATA_SOURCE_NAME")
125+
if len(dsn) != 0 {
126+
return strings.Split(dsn, ","), nil
127+
}
128+
129+
var user, pass, uri string
130+
131+
dataSourceUserFile := os.Getenv("DATA_SOURCE_USER_FILE")
132+
if len(dataSourceUserFile) != 0 {
133+
fileContents, err := ioutil.ReadFile(dataSourceUserFile)
134+
if err != nil {
135+
return nil, fmt.Errorf("failed loading data source user file %s: %s", dataSourceUserFile, err.Error())
136+
}
137+
user = strings.TrimSpace(string(fileContents))
138+
} else {
139+
user = os.Getenv("DATA_SOURCE_USER")
140+
}
141+
142+
dataSourcePassFile := os.Getenv("DATA_SOURCE_PASS_FILE")
143+
if len(dataSourcePassFile) != 0 {
144+
fileContents, err := ioutil.ReadFile(dataSourcePassFile)
145+
if err != nil {
146+
return nil, fmt.Errorf("failed loading data source pass file %s: %s", dataSourcePassFile, err.Error())
147+
}
148+
pass = strings.TrimSpace(string(fileContents))
149+
} else {
150+
pass = os.Getenv("DATA_SOURCE_PASS")
151+
}
152+
153+
ui := url.UserPassword(user, pass).String()
154+
dataSrouceURIFile := os.Getenv("DATA_SOURCE_URI_FILE")
155+
if len(dataSrouceURIFile) != 0 {
156+
fileContents, err := ioutil.ReadFile(dataSrouceURIFile)
157+
if err != nil {
158+
return nil, fmt.Errorf("failed loading data source URI file %s: %s", dataSrouceURIFile, err.Error())
159+
}
160+
uri = strings.TrimSpace(string(fileContents))
161+
} else {
162+
uri = os.Getenv("DATA_SOURCE_URI")
163+
}
164+
165+
dsn = "postgresql://" + ui + "@" + uri
166+
167+
return []string{dsn}, nil
168+
}

Diff for: cmd/postgres_exporter/main.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2021 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"net/http"
18+
"os"
19+
20+
"github.com/go-kit/kit/log"
21+
"github.com/go-kit/kit/log/level"
22+
"github.com/prometheus/client_golang/prometheus"
23+
"github.com/prometheus/client_golang/prometheus/promhttp"
24+
"github.com/prometheus/common/promlog"
25+
"github.com/prometheus/common/promlog/flag"
26+
"github.com/prometheus/common/version"
27+
"github.com/prometheus/exporter-toolkit/web"
28+
webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag"
29+
"gopkg.in/alecthomas/kingpin.v2"
30+
)
31+
32+
var (
33+
listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Default(":9187").Envar("PG_EXPORTER_WEB_LISTEN_ADDRESS").String()
34+
webConfig = webflag.AddFlags(kingpin.CommandLine)
35+
metricPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("PG_EXPORTER_WEB_TELEMETRY_PATH").String()
36+
disableDefaultMetrics = kingpin.Flag("disable-default-metrics", "Do not include default metrics.").Default("false").Envar("PG_EXPORTER_DISABLE_DEFAULT_METRICS").Bool()
37+
disableSettingsMetrics = kingpin.Flag("disable-settings-metrics", "Do not include pg_settings metrics.").Default("false").Envar("PG_EXPORTER_DISABLE_SETTINGS_METRICS").Bool()
38+
autoDiscoverDatabases = kingpin.Flag("auto-discover-databases", "Whether to discover the databases on a server dynamically.").Default("false").Envar("PG_EXPORTER_AUTO_DISCOVER_DATABASES").Bool()
39+
queriesPath = kingpin.Flag("extend.query-path", "Path to custom queries to run.").Default("").Envar("PG_EXPORTER_EXTEND_QUERY_PATH").String()
40+
onlyDumpMaps = kingpin.Flag("dumpmaps", "Do not run, simply dump the maps.").Bool()
41+
constantLabelsList = kingpin.Flag("constantLabels", "A list of label=value separated by comma(,).").Default("").Envar("PG_EXPORTER_CONSTANT_LABELS").String()
42+
excludeDatabases = kingpin.Flag("exclude-databases", "A list of databases to remove when autoDiscoverDatabases is enabled").Default("").Envar("PG_EXPORTER_EXCLUDE_DATABASES").String()
43+
includeDatabases = kingpin.Flag("include-databases", "A list of databases to include when autoDiscoverDatabases is enabled").Default("").Envar("PG_EXPORTER_INCLUDE_DATABASES").String()
44+
metricPrefix = kingpin.Flag("metric-prefix", "A metric prefix can be used to have non-default (not \"pg\") prefixes for each of the metrics").Default("pg").Envar("PG_EXPORTER_METRIC_PREFIX").String()
45+
logger = log.NewNopLogger()
46+
)
47+
48+
// Metric name parts.
49+
const (
50+
// Namespace for all metrics.
51+
namespace = "pg"
52+
// Subsystems.
53+
exporter = "exporter"
54+
// The name of the exporter.
55+
exporterName = "postgres_exporter"
56+
// Metric label used for static string data thats handy to send to Prometheus
57+
// e.g. version
58+
staticLabelName = "static"
59+
// Metric label used for server identification.
60+
serverLabelName = "server"
61+
)
62+
63+
func main() {
64+
kingpin.Version(version.Print(exporterName))
65+
promlogConfig := &promlog.Config{}
66+
flag.AddFlags(kingpin.CommandLine, promlogConfig)
67+
kingpin.HelpFlag.Short('h')
68+
kingpin.Parse()
69+
logger = promlog.New(promlogConfig)
70+
71+
// landingPage contains the HTML served at '/'.
72+
// TODO: Make this nicer and more informative.
73+
var landingPage = []byte(`<html>
74+
<head><title>Postgres exporter</title></head>
75+
<body>
76+
<h1>Postgres exporter</h1>
77+
<p><a href='` + *metricPath + `'>Metrics</a></p>
78+
</body>
79+
</html>
80+
`)
81+
82+
if *onlyDumpMaps {
83+
dumpMaps()
84+
return
85+
}
86+
87+
dsn, err := getDataSources()
88+
if err != nil {
89+
level.Error(logger).Log("msg", "Failed reading data sources", "err", err.Error())
90+
os.Exit(1)
91+
}
92+
93+
if len(dsn) == 0 {
94+
level.Error(logger).Log("msg", "Couldn't find environment variables describing the datasource to use")
95+
os.Exit(1)
96+
}
97+
98+
opts := []ExporterOpt{
99+
DisableDefaultMetrics(*disableDefaultMetrics),
100+
DisableSettingsMetrics(*disableSettingsMetrics),
101+
AutoDiscoverDatabases(*autoDiscoverDatabases),
102+
WithUserQueriesPath(*queriesPath),
103+
WithConstantLabels(*constantLabelsList),
104+
ExcludeDatabases(*excludeDatabases),
105+
IncludeDatabases(*includeDatabases),
106+
}
107+
108+
exporter := NewExporter(dsn, opts...)
109+
defer func() {
110+
exporter.servers.Close()
111+
}()
112+
113+
prometheus.MustRegister(version.NewCollector(exporterName))
114+
115+
prometheus.MustRegister(exporter)
116+
117+
http.Handle(*metricPath, promhttp.Handler())
118+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
119+
w.Header().Set("Content-Type", "text/html; charset=UTF-8") // nolint: errcheck
120+
w.Write(landingPage) // nolint: errcheck
121+
})
122+
123+
level.Info(logger).Log("msg", "Listening on address", "address", *listenAddress)
124+
srv := &http.Server{Addr: *listenAddress}
125+
if err := web.ListenAndServe(srv, *webConfig, logger); err != nil {
126+
level.Error(logger).Log("msg", "Error running HTTP server", "err", err)
127+
os.Exit(1)
128+
}
129+
}

0 commit comments

Comments
 (0)