-
Notifications
You must be signed in to change notification settings - Fork 485
/
postgres_exporter.go
149 lines (125 loc) · 4.63 KB
/
postgres_exporter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Package postgres_exporter embeds https://github.com/prometheus/postgres_exporter
package postgres_exporter //nolint:golint
import (
"fmt"
"os"
"strings"
config_util "github.com/prometheus/common/config"
"github.com/go-kit/log"
"github.com/grafana/agent/pkg/integrations"
integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"
"github.com/grafana/agent/pkg/integrations/v2/metricsutils"
"github.com/lib/pq"
"github.com/prometheus-community/postgres_exporter/exporter"
)
// Config controls the postgres_exporter integration.
type Config struct {
// DataSourceNames to use to connect to Postgres.
DataSourceNames []config_util.Secret `yaml:"data_source_names,omitempty"`
DisableSettingsMetrics bool `yaml:"disable_settings_metrics,omitempty"`
AutodiscoverDatabases bool `yaml:"autodiscover_databases,omitempty"`
ExcludeDatabases []string `yaml:"exclude_databases,omitempty"`
IncludeDatabases []string `yaml:"include_databases,omitempty"`
DisableDefaultMetrics bool `yaml:"disable_default_metrics,omitempty"`
QueryPath string `yaml:"query_path,omitempty"`
}
// Name returns the name of the integration this config is for.
func (c *Config) Name() string {
return "postgres_exporter"
}
// NewIntegration converts this config into an instance of a configuration.
func (c *Config) NewIntegration(l log.Logger) (integrations.Integration, error) {
return New(l, c)
}
// InstanceKey returns a simplified DSN of the first postgresql DSN, or an error if
// not exactly one DSN is provided.
func (c *Config) InstanceKey(_ string) (string, error) {
dsn, err := c.getDataSourceNames()
if err != nil {
return "", err
}
if len(dsn) != 1 {
return "", fmt.Errorf("can't automatically determine a value for `instance` with %d DSN. either use 1 DSN or manually assign a value for `instance` in the integration config", len(dsn))
}
s, err := parsePostgresURL(dsn[0])
if err != nil {
return "", fmt.Errorf("cannot parse DSN: %w", err)
}
// Assign default values to s.
//
// PostgreSQL hostspecs can contain multiple host pairs. We'll assign a host
// and port by default, but otherwise just use the hostname.
if _, ok := s["host"]; !ok {
s["host"] = "localhost"
s["port"] = "5432"
}
hostport := s["host"]
if p, ok := s["port"]; ok {
hostport += fmt.Sprintf(":%s", p)
}
return fmt.Sprintf("postgresql://%s/%s", hostport, s["dbname"]), nil
}
func parsePostgresURL(url string) (map[string]string, error) {
raw, err := pq.ParseURL(url)
if err != nil {
return nil, err
}
res := map[string]string{}
unescaper := strings.NewReplacer(`\'`, `'`, `\\`, `\`)
for _, keypair := range strings.Split(raw, " ") {
parts := strings.SplitN(keypair, "=", 2)
if len(parts) != 2 {
panic(fmt.Sprintf("unexpected keypair %s from pq", keypair))
}
key := parts[0]
value := parts[1]
// Undo all the transformations ParseURL did: remove wrapping
// quotes and then unescape the escaped characters.
value = strings.TrimPrefix(value, "'")
value = strings.TrimSuffix(value, "'")
value = unescaper.Replace(value)
res[key] = value
}
return res, nil
}
// getDataSourceNames loads data source names from the config or from the
// environment, if set.
func (c *Config) getDataSourceNames() ([]string, error) {
dsn := c.DataSourceNames
var stringDsn []string
if len(dsn) == 0 {
stringDsn = append(stringDsn, strings.Split(os.Getenv("POSTGRES_EXPORTER_DATA_SOURCE_NAME"), ",")...)
} else {
for _, d := range dsn {
stringDsn = append(stringDsn, string(d))
}
}
if len(stringDsn) == 0 {
return nil, fmt.Errorf("cannot create postgres_exporter; neither postgres_exporter.data_source_name or $POSTGRES_EXPORTER_DATA_SOURCE_NAME is set")
}
return stringDsn, nil
}
func init() {
integrations.RegisterIntegration(&Config{})
integrations_v2.RegisterLegacy(&Config{}, integrations_v2.TypeMultiplex, metricsutils.NewNamedShim("postgres"))
}
// New creates a new postgres_exporter integration. The integration scrapes
// metrics from a postgres process.
func New(log log.Logger, c *Config) (integrations.Integration, error) {
dsn, err := c.getDataSourceNames()
if err != nil {
return nil, err
}
e := exporter.NewExporter(
dsn,
log,
exporter.DisableDefaultMetrics(c.DisableDefaultMetrics),
exporter.WithUserQueriesPath(c.QueryPath),
exporter.DisableSettingsMetrics(c.DisableSettingsMetrics),
exporter.AutoDiscoverDatabases(c.AutodiscoverDatabases),
exporter.ExcludeDatabases(strings.Join(c.ExcludeDatabases, ",")),
exporter.IncludeDatabases(strings.Join(c.IncludeDatabases, ",")),
exporter.MetricPrefix("pg"),
)
return integrations.NewCollectorIntegration(c.Name(), integrations.WithCollectors(e)), nil
}