diff --git a/README.md b/README.md index 105cd0d25..fa8e6c272 100644 --- a/README.md +++ b/README.md @@ -121,9 +121,9 @@ For running it locally on a default Debian/Ubuntu install, this will work (trans sudo -u postgres DATA_SOURCE_NAME="user=postgres host=/var/run/postgresql/ sslmode=disable" postgres_exporter -Also, you can set a list of sources to scrape different instances from the one exporter setup. Just define a string separated by comma. +Also, you can set a list of sources to scrape different instances from the one exporter setup. Just define a comma separated string. - sudo -u postgres DATA_SOURCE_NAME="port=5432, port=6432" postgres_exporter + sudo -u postgres DATA_SOURCE_NAME="port=5432,port=6432" postgres_exporter See the [github.com/lib/pq](http://github.com/lib/pq) module for other ways to format the connection string. @@ -155,7 +155,8 @@ flag. This removes all built-in metrics, and uses only metrics defined by querie ### Running as non-superuser -To be able to collect metrics from pg_stat_activity and pg_stat_replication as non-superuser you have to create views as a superuser, and assign permissions separately to those. In PostgreSQL, views run with the permissions of the user that created them so they can act as security barriers. +To be able to collect metrics from `pg_stat_activity` and `pg_stat_replication` as non-superuser you have to create views as a superuser, and assign permissions separately to those. +In PostgreSQL, views run with the permissions of the user that created them so they can act as security barriers. ```sql CREATE USER postgres_exporter PASSWORD 'password'; diff --git a/cmd/postgres_exporter/pg_setting.go b/cmd/postgres_exporter/pg_setting.go index a03178c74..f4060ba31 100644 --- a/cmd/postgres_exporter/pg_setting.go +++ b/cmd/postgres_exporter/pg_setting.go @@ -33,7 +33,7 @@ func querySettings(ch chan<- prometheus.Metric, server *Server) error { return fmt.Errorf("Error retrieving rows on %q: %s %v", server, namespace, err) } - ch <- s.metric(server.String()) + ch <- s.metric(server.labels) } return nil @@ -45,7 +45,7 @@ type pgSetting struct { name, setting, unit, shortDesc, vartype string } -func (s *pgSetting) metric(server string) prometheus.Metric { +func (s *pgSetting) metric(labels prometheus.Labels) prometheus.Metric { var ( err error name = strings.Replace(s.name, ".", "_", -1) @@ -76,8 +76,8 @@ func (s *pgSetting) metric(server string) prometheus.Metric { panic(fmt.Sprintf("Unsupported vartype %q", s.vartype)) } - desc := newDesc(subsystem, name, shortDesc) - return prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val, server) + desc := newDesc(subsystem, name, shortDesc, labels) + return prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val) } // TODO: fix linter override diff --git a/cmd/postgres_exporter/pg_setting_test.go b/cmd/postgres_exporter/pg_setting_test.go index b69c039c3..3d7820ed3 100644 --- a/cmd/postgres_exporter/pg_setting_test.go +++ b/cmd/postgres_exporter/pg_setting_test.go @@ -3,6 +3,7 @@ package main import ( + "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" . "gopkg.in/check.v1" ) @@ -25,7 +26,7 @@ var fixtures = []fixture{ unit: "seconds", err: "", }, - d: `Desc{fqName: "pg_settings_seconds_fixture_metric_seconds", help: "Foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_seconds_fixture_metric_seconds", help: "Foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: []}`, v: 5, }, { @@ -41,7 +42,7 @@ var fixtures = []fixture{ unit: "seconds", err: "", }, - d: `Desc{fqName: "pg_settings_milliseconds_fixture_metric_seconds", help: "Foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_milliseconds_fixture_metric_seconds", help: "Foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: []}`, v: 5, }, { @@ -57,7 +58,7 @@ var fixtures = []fixture{ unit: "bytes", err: "", }, - d: `Desc{fqName: "pg_settings_eight_kb_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_eight_kb_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: []}`, v: 139264, }, { @@ -73,7 +74,7 @@ var fixtures = []fixture{ unit: "bytes", err: "", }, - d: `Desc{fqName: "pg_settings_16_kb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_16_kb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: []}`, v: 49152, }, { @@ -89,7 +90,7 @@ var fixtures = []fixture{ unit: "bytes", err: "", }, - d: `Desc{fqName: "pg_settings_16_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_16_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: []}`, v: 5.0331648e+07, }, { @@ -105,7 +106,7 @@ var fixtures = []fixture{ unit: "bytes", err: "", }, - d: `Desc{fqName: "pg_settings_32_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_32_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: []}`, v: 1.00663296e+08, }, { @@ -121,7 +122,7 @@ var fixtures = []fixture{ unit: "bytes", err: "", }, - d: `Desc{fqName: "pg_settings_64_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_64_mb_real_fixture_metric_bytes", help: "Foo foo foo [Units converted to bytes.]", constLabels: {}, variableLabels: []}`, v: 2.01326592e+08, }, { @@ -137,7 +138,7 @@ var fixtures = []fixture{ unit: "", err: "", }, - d: `Desc{fqName: "pg_settings_bool_on_fixture_metric", help: "Foo foo foo", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_bool_on_fixture_metric", help: "Foo foo foo", constLabels: {}, variableLabels: []}`, v: 1, }, { @@ -153,7 +154,7 @@ var fixtures = []fixture{ unit: "", err: "", }, - d: `Desc{fqName: "pg_settings_bool_off_fixture_metric", help: "Foo foo foo", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_bool_off_fixture_metric", help: "Foo foo foo", constLabels: {}, variableLabels: []}`, v: 0, }, { @@ -169,7 +170,7 @@ var fixtures = []fixture{ unit: "seconds", err: "", }, - d: `Desc{fqName: "pg_settings_special_minus_one_value_seconds", help: "foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_special_minus_one_value_seconds", help: "foo foo foo [Units converted to seconds.]", constLabels: {}, variableLabels: []}`, v: -1, }, { @@ -185,7 +186,7 @@ var fixtures = []fixture{ unit: "", err: "", }, - d: `Desc{fqName: "pg_settings_rds_rds_superuser_reserved_connections", help: "Sets the number of connection slots reserved for rds_superusers.", constLabels: {}, variableLabels: [` + serverLabelName + `]}`, + d: `Desc{fqName: "pg_settings_rds_rds_superuser_reserved_connections", help: "Sets the number of connection slots reserved for rds_superusers.", constLabels: {}, variableLabels: []}`, v: 2, }, { @@ -233,7 +234,7 @@ func (s *PgSettingSuite) TestMetric(c *C) { for _, f := range fixtures { d := &dto.Metric{} - m := f.p.metric("") + m := f.p.metric(prometheus.Labels{}) m.Write(d) // nolint: errcheck c.Check(m.Desc().String(), Equals, f.d) diff --git a/cmd/postgres_exporter/postgres_exporter.go b/cmd/postgres_exporter/postgres_exporter.go index 15c17b393..2f1937d93 100644 --- a/cmd/postgres_exporter/postgres_exporter.go +++ b/cmd/postgres_exporter/postgres_exporter.go @@ -452,7 +452,7 @@ func addQueries(content []byte, pgVersion semver.Version, server *Server) error } // Convert the loaded metric map into exporter representation - partialExporterMap := makeDescMap(pgVersion, metricMaps) + partialExporterMap := makeDescMap(pgVersion, server.labels, metricMaps) // Merge the two maps (which are now quite flatteend) for k, v := range partialExporterMap { @@ -480,7 +480,7 @@ func addQueries(content []byte, pgVersion semver.Version, server *Server) error } // Turn the MetricMap column mapping into a prometheus descriptor mapping. -func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]ColumnMapping) map[string]MetricMapNamespace { +func makeDescMap(pgVersion semver.Version, serverLabels prometheus.Labels, metricMaps map[string]map[string]ColumnMapping) map[string]MetricMapNamespace { var metricMap = make(map[string]MetricMapNamespace) for namespace, mappings := range metricMaps { @@ -488,7 +488,7 @@ func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]Colu // Get the constant labels. // Server label must be added to each metric. - constLabels := []string{serverLabelName} + var constLabels []string for columnName, columnMapping := range mappings { if columnMapping.usage == LABEL { constLabels = append(constLabels, columnName) @@ -526,7 +526,7 @@ func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]Colu case COUNTER: thisMap[columnName] = MetricMap{ vtype: prometheus.CounterValue, - desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil), + desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, serverLabels), conversion: func(in interface{}) (float64, bool) { return dbToFloat64(in) }, @@ -534,7 +534,7 @@ func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]Colu case GAUGE: thisMap[columnName] = MetricMap{ vtype: prometheus.GaugeValue, - desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil), + desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, serverLabels), conversion: func(in interface{}) (float64, bool) { return dbToFloat64(in) }, @@ -542,7 +542,7 @@ func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]Colu case MAPPEDMETRIC: thisMap[columnName] = MetricMap{ vtype: prometheus.GaugeValue, - desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, nil), + desc: prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, columnName), columnMapping.description, constLabels, serverLabels), conversion: func(in interface{}) (float64, bool) { text, ok := in.(string) if !ok { @@ -559,7 +559,7 @@ func makeDescMap(pgVersion semver.Version, metricMaps map[string]map[string]Colu case DURATION: thisMap[columnName] = MetricMap{ vtype: prometheus.GaugeValue, - desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, constLabels, nil), + desc: prometheus.NewDesc(fmt.Sprintf("%s_%s_milliseconds", namespace, columnName), columnMapping.description, constLabels, serverLabels), conversion: func(in interface{}) (float64, bool) { var durationString string switch t := in.(type) { @@ -725,8 +725,8 @@ func parseDSN(dsn string) (*url.URL, error) { // Server describes a connection to Postgres. // Also it contains metrics map and query overrides. type Server struct { - db *sql.DB - fingerprint string + db *sql.DB + labels prometheus.Labels // Last version used to calculate metric map. If mismatch on scrape, // then maps are recalculated. @@ -755,8 +755,10 @@ func NewServer(dsn string) (*Server, error) { log.Infof("Established new database connection to %q.", fingerprint) return &Server{ - db: db, - fingerprint: fingerprint, + db: db, + labels: prometheus.Labels{ + serverLabelName: fingerprint, + }, }, nil } @@ -784,7 +786,7 @@ func (s *Server) Ping() error { // String returns server's fingerprint. func (s *Server) String() string { - return s.fingerprint + return s.labels[serverLabelName] } // Scrape loads metrics. @@ -976,10 +978,10 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { e.userQueriesError.Collect(ch) } -func newDesc(subsystem, name, help string) *prometheus.Desc { +func newDesc(subsystem, name, help string, labels prometheus.Labels) *prometheus.Desc { return prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, name), - help, []string{serverLabelName}, nil, + help, nil, labels, ) } @@ -1040,9 +1042,8 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, server *Server, namespac // Get the label values for this row. labels := make([]string, len(mapping.labels)) - labels[0] = server.String() // Server label must be added to each metric. - for idx := 1; idx < len(mapping.labels); idx++ { - labels[idx], _ = dbToString(columnData[columnIdx[mapping.labels[idx]]]) + for idx, label := range mapping.labels { + labels[idx], _ = dbToString(columnData[columnIdx[label]]) } // Loop over column names, and match to scan data. Unknown columns @@ -1133,7 +1134,7 @@ func (e *Exporter) checkMapVersions(ch chan<- prometheus.Metric, server *Server) server.metricMap = make(map[string]MetricMapNamespace) server.queryOverrides = make(map[string]string) } else { - server.metricMap = makeDescMap(semanticVersion, e.builtinMetricMaps) + server.metricMap = makeDescMap(semanticVersion, server.labels, e.builtinMetricMaps) server.queryOverrides = makeQueryOverrideMap(semanticVersion, queryOverrides) } @@ -1166,10 +1167,10 @@ func (e *Exporter) checkMapVersions(ch chan<- prometheus.Metric, server *Server) // Output the version as a special metric versionDesc := prometheus.NewDesc(fmt.Sprintf("%s_%s", namespace, staticLabelName), - "Version string as reported by postgres", []string{serverLabelName, "version", "short_version"}, nil) + "Version string as reported by postgres", []string{"version", "short_version"}, server.labels) ch <- prometheus.MustNewConstMetric(versionDesc, - prometheus.UntypedValue, 1, server.String(), versionString, semanticVersion.String()) + prometheus.UntypedValue, 1, versionString, semanticVersion.String()) return nil } diff --git a/cmd/postgres_exporter/postgres_exporter_test.go b/cmd/postgres_exporter/postgres_exporter_test.go index 72aa5db93..598c8a62c 100644 --- a/cmd/postgres_exporter/postgres_exporter_test.go +++ b/cmd/postgres_exporter/postgres_exporter_test.go @@ -10,6 +10,7 @@ import ( "os" "github.com/blang/semver" + "github.com/prometheus/client_golang/prometheus" ) // Hook up gocheck into the "go test" runner. @@ -34,7 +35,7 @@ func (s *FunctionalSuite) TestSemanticVersionColumnDiscard(c *C) { { // No metrics should be eliminated - resultMap := makeDescMap(semver.MustParse("0.0.1"), testMetricMap) + resultMap := makeDescMap(semver.MustParse("0.0.1"), prometheus.Labels{}, testMetricMap) c.Check( resultMap["test_namespace"].columnMappings["metric_which_stays"].discard, Equals, @@ -55,7 +56,7 @@ func (s *FunctionalSuite) TestSemanticVersionColumnDiscard(c *C) { testMetricMap["test_namespace"]["metric_which_discards"] = discardableMetric // Discard metric should be discarded - resultMap := makeDescMap(semver.MustParse("0.0.1"), testMetricMap) + resultMap := makeDescMap(semver.MustParse("0.0.1"), prometheus.Labels{}, testMetricMap) c.Check( resultMap["test_namespace"].columnMappings["metric_which_stays"].discard, Equals, @@ -76,7 +77,7 @@ func (s *FunctionalSuite) TestSemanticVersionColumnDiscard(c *C) { testMetricMap["test_namespace"]["metric_which_discards"] = discardableMetric // Discard metric should be discarded - resultMap := makeDescMap(semver.MustParse("0.0.2"), testMetricMap) + resultMap := makeDescMap(semver.MustParse("0.0.2"), prometheus.Labels{}, testMetricMap) c.Check( resultMap["test_namespace"].columnMappings["metric_which_stays"].discard, Equals,