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