diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..a02c9a282 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.22.3 diff --git a/cmd/postgres_exporter/main.go b/cmd/postgres_exporter/main.go index f4d454996..1c9f98da3 100644 --- a/cmd/postgres_exporter/main.go +++ b/cmd/postgres_exporter/main.go @@ -22,8 +22,6 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/prometheus-community/postgres_exporter/collector" - "github.com/prometheus-community/postgres_exporter/config" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promlog" @@ -31,6 +29,8 @@ import ( "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web/kingpinflag" + "github.com/zeet-dev/postgres_exporter/collector" + "github.com/zeet-dev/postgres_exporter/config" ) var ( diff --git a/cmd/postgres_exporter/pg_setting.go b/cmd/postgres_exporter/pg_setting.go index f162c354a..213759ede 100644 --- a/cmd/postgres_exporter/pg_setting.go +++ b/cmd/postgres_exporter/pg_setting.go @@ -102,7 +102,7 @@ func (s *pgSetting) metric(labels prometheus.Labels) prometheus.Metric { // Removes units from any of the setting values. // This is mostly because of a irregularity regarding AWS RDS Aurora -// https://github.com/prometheus-community/postgres_exporter/issues/619 +// https://github.com/zeet-dev/postgres_exporter/issues/619 func (s *pgSetting) sanitizeValue() { for _, unit := range settingUnits { if strings.HasSuffix(s.setting, unit) { diff --git a/cmd/postgres_exporter/postgres_exporter_integration_test.go b/cmd/postgres_exporter/postgres_exporter_integration_test.go index 7043c1e88..c896ab98a 100644 --- a/cmd/postgres_exporter/postgres_exporter_integration_test.go +++ b/cmd/postgres_exporter/postgres_exporter_integration_test.go @@ -89,7 +89,7 @@ func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) { } // TestInvalidDsnDoesntCrash tests that specifying an invalid DSN doesn't crash -// the exporter. Related to https://github.com/prometheus-community/postgres_exporter/issues/93 +// the exporter. Related to https://github.com/zeet-dev/postgres_exporter/issues/93 // although not a replication of the scenario. func (s *IntegrationSuite) TestInvalidDsnDoesntCrash(c *C) { // Setup a dummy channel to consume metrics diff --git a/cmd/postgres_exporter/probe.go b/cmd/postgres_exporter/probe.go index 5945e07b8..e2188af4d 100644 --- a/cmd/postgres_exporter/probe.go +++ b/cmd/postgres_exporter/probe.go @@ -19,10 +19,10 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/prometheus-community/postgres_exporter/collector" - "github.com/prometheus-community/postgres_exporter/config" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/zeet-dev/postgres_exporter/collector" + "github.com/zeet-dev/postgres_exporter/config" ) func handleProbe(logger log.Logger, excludeDatabases []string) http.HandlerFunc { diff --git a/collector/collector.go b/collector/collector.go index 121129871..9e1afb061 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -67,20 +67,7 @@ type collectorConfig struct { } func registerCollector(name string, isDefaultEnabled bool, createFunc func(collectorConfig) (Collector, error)) { - var helpDefaultState string - if isDefaultEnabled { - helpDefaultState = "enabled" - } else { - helpDefaultState = "disabled" - } - - // Create flag for this collector - flagName := fmt.Sprintf("collector.%s", name) - flagHelp := fmt.Sprintf("Enable the %s collector (default: %s).", name, helpDefaultState) - defaultValue := fmt.Sprintf("%v", isDefaultEnabled) - - flag := kingpin.Flag(flagName, flagHelp).Default(defaultValue).Action(collectorFlagAction(name)).Bool() - collectorState[name] = flag + collectorState[name] = &isDefaultEnabled // Register the create function for this collector factories[name] = createFunc diff --git a/collector/pg_postmaster.go b/collector/pg_postmaster.go index b81e4f905..08446a544 100644 --- a/collector/pg_postmaster.go +++ b/collector/pg_postmaster.go @@ -23,7 +23,7 @@ import ( const postmasterSubsystem = "postmaster" func init() { - registerCollector(postmasterSubsystem, defaultDisabled, NewPGPostmasterCollector) + registerCollector(postmasterSubsystem, defaultEnabled, NewPGPostmasterCollector) } type PGPostmasterCollector struct { diff --git a/collector/pg_setting.go b/collector/pg_setting.go new file mode 100644 index 000000000..d42392c1d --- /dev/null +++ b/collector/pg_setting.go @@ -0,0 +1,215 @@ +// Copyright 2023 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package collector + +import ( + "context" + "fmt" + "math" + "strconv" + "strings" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" +) + +const settingSubsystem = "setting" + +func init() { + registerCollector(settingSubsystem, defaultEnabled, NewPGSettingCollector) +} + +type PGSettingCollector struct { + log log.Logger +} + +func NewPGSettingCollector(config collectorConfig) (Collector, error) { + return &PGSettingCollector{ + log: config.logger, + }, nil +} + +var ( + settingUnits = []string{ + "ms", "s", "min", "h", "d", + "B", "kB", "MB", "GB", "TB", + } +) + +// Query the pg_settings view containing runtime variables +func querySettings(ch chan<- prometheus.Metric, instance *instance, logger log.Logger) error { + level.Debug(logger).Log("msg", "Querying pg_setting view", "instance", instance) + + // pg_settings docs: https://www.postgresql.org/docs/current/static/view-pg-settings.html + // + // NOTE: If you add more vartypes here, you must update the supported + // types in normaliseUnit() below + query := "SELECT name, setting, COALESCE(unit, ''), short_desc, vartype FROM pg_settings WHERE vartype IN ('bool', 'integer', 'real') AND name != 'sync_commit_cancel_wait';" + + rows, err := instance.db.Query(query) + if err != nil { + return fmt.Errorf("Error running query on database %q: %s %v", instance, namespace, err) + } + defer rows.Close() // nolint: errcheck + + for rows.Next() { + s := &pgSetting{} + err = rows.Scan(&s.name, &s.setting, &s.unit, &s.shortDesc, &s.vartype) + if err != nil { + return fmt.Errorf("Error retrieving rows on %q: %s %v", instance, namespace, err) + } + + ch <- s.metric(prometheus.Labels{}) + } + + return nil +} + +func (c *PGSettingCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error { + return querySettings(ch, instance, c.log) +} + +// pgSetting is represents a PostgreSQL runtime variable as returned by the +// pg_settings view. +type pgSetting struct { + name, setting, unit, shortDesc, vartype string +} + +func (s *pgSetting) metric(labels prometheus.Labels) prometheus.Metric { + var ( + err error + name = strings.Replace(s.name, ".", "_", -1) + unit = s.unit // nolint: ineffassign + shortDesc = fmt.Sprintf("Server Parameter: %s", s.name) + subsystem = "settings" + val float64 + ) + + switch s.vartype { + case "bool": + if s.setting == "on" { + val = 1 + } + case "integer", "real": + if val, unit, err = s.normaliseUnit(); err != nil { + // Panic, since we should recognise all units + // and don't want to silently exlude metrics + panic(err) + } + + if len(unit) > 0 { + name = fmt.Sprintf("%s_%s", name, unit) + shortDesc = fmt.Sprintf("%s [Units converted to %s.]", shortDesc, unit) + } + default: + // Panic because we got a type we didn't ask for + panic(fmt.Sprintf("Unsupported vartype %q", s.vartype)) + } + + desc := prometheus.NewDesc( + prometheus.BuildFQName( + namespace, + subsystem, + name, + ), + shortDesc, + []string{}, labels, + ) + + return prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val) +} + +// Removes units from any of the setting values. +// This is mostly because of a irregularity regarding AWS RDS Aurora +// https://github.com/zeet-dev/postgres_exporter/issues/619 +func (s *pgSetting) sanitizeValue() { + for _, unit := range settingUnits { + if strings.HasSuffix(s.setting, unit) { + endPos := len(s.setting) - len(unit) - 1 + s.setting = s.setting[:endPos] + return + } + } +} + +// TODO: fix linter override +// nolint: nakedret +func (s *pgSetting) normaliseUnit() (val float64, unit string, err error) { + s.sanitizeValue() + + val, err = strconv.ParseFloat(s.setting, 64) + if err != nil { + return val, unit, fmt.Errorf("Error converting setting %q value %q to float: %s", s.name, s.setting, err) + } + + // Units defined in: https://www.postgresql.org/docs/current/static/config-setting.html + switch s.unit { + case "": + return + case "ms", "s", "min", "h", "d": + unit = "seconds" + case "B", "kB", "MB", "GB", "TB", "1kB", "2kB", "4kB", "8kB", "16kB", "32kB", "64kB", "16MB", "32MB", "64MB": + unit = "bytes" + default: + err = fmt.Errorf("Unknown unit for runtime variable: %q", s.unit) + return + } + + // -1 is special, don't modify the value + if val == -1 { + return + } + + switch s.unit { + case "ms": + val /= 1000 + case "min": + val *= 60 + case "h": + val *= 60 * 60 + case "d": + val *= 60 * 60 * 24 + case "kB": + val *= math.Pow(2, 10) + case "MB": + val *= math.Pow(2, 20) + case "GB": + val *= math.Pow(2, 30) + case "TB": + val *= math.Pow(2, 40) + case "1kB": + val *= math.Pow(2, 10) + case "2kB": + val *= math.Pow(2, 11) + case "4kB": + val *= math.Pow(2, 12) + case "8kB": + val *= math.Pow(2, 13) + case "16kB": + val *= math.Pow(2, 14) + case "32kB": + val *= math.Pow(2, 15) + case "64kB": + val *= math.Pow(2, 16) + case "16MB": + val *= math.Pow(2, 24) + case "32MB": + val *= math.Pow(2, 25) + case "64MB": + val *= math.Pow(2, 26) + } + + return +} diff --git a/collector/probe.go b/collector/probe.go index 4c0f0419b..9fc1d5bb9 100644 --- a/collector/probe.go +++ b/collector/probe.go @@ -19,8 +19,8 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/prometheus-community/postgres_exporter/config" "github.com/prometheus/client_golang/prometheus" + "github.com/zeet-dev/postgres_exporter/config" ) type ProbeCollector struct { diff --git a/go.mod b/go.mod index e937de61d..94d6d89d1 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/prometheus-community/postgres_exporter +module github.com/zeet-dev/postgres_exporter go 1.19