Skip to content

Commit 2cf60d6

Browse files
committed
Add all bool/integer/real runtime variables
Use the `pg_settings` view to retrieve runtime variables: https://www.postgresql.org/docs/current/static/view-pg-settings.html This replaces the use of `SHOW` to retrieve runtime variables. In PostgreSQL 9.6, this adds 189 metrics, which use the `short_desc` field as a description. Only runtime variables with a `vartype` of `bool`, `real`, or `integer` are currently supported. Example metrics: # HELP pg_settings_allow_system_table_mods Allows modifications of the structure of system tables. # TYPE pg_settings_allow_system_table_mods gauge pg_settings_allow_system_table_mods 0 # HELP pg_settings_archive_timeout_seconds Forces a switch to the next xlog file if a new file has not been started within N seconds. [Converted to seconds.] # TYPE pg_settings_archive_timeout_seconds gauge pg_settings_archive_timeout_seconds 0 # HELP pg_settings_array_nulls Enable input of NULL elements in arrays. # TYPE pg_settings_array_nulls gauge pg_settings_array_nulls 1 # HELP pg_settings_authentication_timeout_seconds Sets the maximum allowed time to complete client authentication. [Converted to seconds.] # TYPE pg_settings_authentication_timeout_seconds gauge pg_settings_authentication_timeout_seconds 60 # HELP pg_settings_autovacuum Starts the autovacuum subprocess. # TYPE pg_settings_autovacuum gauge pg_settings_autovacuum 1 # HELP pg_settings_autovacuum_analyze_scale_factor Number of tuple inserts, updates, or deletes prior to analyze as a fraction of reltuples. # TYPE pg_settings_autovacuum_analyze_scale_factor gauge pg_settings_autovacuum_analyze_scale_factor 0.1
1 parent 0b003b2 commit 2cf60d6

File tree

2 files changed

+117
-64
lines changed

2 files changed

+117
-64
lines changed

postgres_exporter.go

Lines changed: 112 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,6 @@ const (
9494
DURATION ColumnUsage = iota // This column should be interpreted as a text duration (and converted to milliseconds)
9595
)
9696

97-
// Special case matric mappings
98-
const (
99-
// Which metric mapping should be acquired using "SHOW" queries
100-
SHOW_METRIC = "pg_runtime_variables"
101-
)
102-
10397
// Regex used to get the "short-version" from the postgres version field.
10498
var versionRegex = regexp.MustCompile(`^\w+ (\d+\.\d+\.\d+)`)
10599
var lowestSupportedVersion = semver.MustParse("9.1.0")
@@ -146,24 +140,6 @@ type MetricMap struct {
146140
conversion func(interface{}) (float64, bool) // Conversion function to turn PG result into float64
147141
}
148142

149-
// Metric descriptors for dynamically created metrics.
150-
var variableMaps = map[string]map[string]ColumnMapping{
151-
"pg_runtime_variable": {
152-
"max_connections": {GAUGE, "Sets the maximum number of concurrent connections.", nil, nil},
153-
"max_files_per_process": {GAUGE, "Sets the maximum number of simultaneously open files for each server process.", nil, nil},
154-
"max_function_args": {GAUGE, "Shows the maximum number of function arguments.", nil, nil},
155-
"max_identifier_length": {GAUGE, "Shows the maximum identifier length.", nil, nil},
156-
"max_index_keys": {GAUGE, "Shows the maximum number of index keys.", nil, nil},
157-
"max_locks_per_transaction": {GAUGE, "Sets the maximum number of locks per transaction.", nil, nil},
158-
"max_pred_locks_per_transaction": {GAUGE, "Sets the maximum number of predicate locks per transaction.", nil, nil},
159-
"max_prepared_transactions": {GAUGE, "Sets the maximum number of simultaneously prepared transactions.", nil, nil},
160-
//"max_stack_depth" : { GAUGE, "Sets the maximum number of concurrent connections.", nil }, // No dehumanize support yet
161-
"max_standby_archive_delay": {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing archived WAL data.", nil, nil},
162-
"max_standby_streaming_delay": {DURATION, "Sets the maximum delay before canceling queries when a hot standby server is processing streamed WAL data.", nil, nil},
163-
"max_wal_senders": {GAUGE, "Sets the maximum number of simultaneously running WAL sender processes.", nil, nil},
164-
},
165-
}
166-
167143
// TODO: revisit this with the semver system
168144
func dumpMaps() {
169145
for name, cmap := range metricMaps {
@@ -693,8 +669,6 @@ type Exporter struct {
693669
// Last version used to calculate metric map. If mismatch on scrape,
694670
// then maps are recalculated.
695671
lastMapVersion semver.Version
696-
// Currently active variable map
697-
variableMap map[string]MetricMapNamespace
698672
// Currently active metric map
699673
metricMap map[string]MetricMapNamespace
700674
// Currently active query overrides
@@ -725,7 +699,6 @@ func NewExporter(dsn string, userQueriesPath string) *Exporter {
725699
Name: "last_scrape_error",
726700
Help: "Whether the last scrape of metrics from PostgreSQL resulted in an error (1 for error, 0 for success).",
727701
}),
728-
variableMap: nil,
729702
metricMap: nil,
730703
queryOverrides: nil,
731704
}
@@ -775,40 +748,124 @@ func newDesc(subsystem, name, help string) *prometheus.Desc {
775748
)
776749
}
777750

778-
// Query the SHOW variables from the query map
779-
// TODO: make this more functional
780-
func queryShowVariables(ch chan<- prometheus.Metric, db *sql.DB, variableMap map[string]MetricMapNamespace) []error {
781-
log.Debugln("Querying SHOW variables")
782-
nonFatalErrors := []error{}
783-
784-
for _, mapping := range variableMap {
785-
for columnName, columnMapping := range mapping.columnMappings {
786-
// Check for a discard request on this value
787-
if columnMapping.discard {
788-
continue
789-
}
751+
// Query the pg_settings view containing runtime variables
752+
func querySettings(ch chan<- prometheus.Metric, db *sql.DB) error {
753+
log.Debugln("Querying pg_setting view")
754+
subsystem := "settings"
790755

791-
// Use SHOW to get the value
792-
row := db.QueryRow(fmt.Sprintf("SHOW %s;", columnName))
756+
// pg_settings docs: https://www.postgresql.org/docs/current/static/view-pg-settings.html
757+
query := "SELECT name, setting, COALESCE(unit, ''), short_desc, vartype FROM pg_settings WHERE vartype IN ('bool', 'integer', 'real');"
793758

794-
var val interface{}
795-
err := row.Scan(&val)
796-
if err != nil {
797-
nonFatalErrors = append(nonFatalErrors, errors.New(fmt.Sprintln("Error scanning runtime variable:", columnName, err)))
798-
continue
759+
rows, err := db.Query(query)
760+
if err != nil {
761+
return errors.New(fmt.Sprintln("Error running query on database: ", namespace, err))
762+
}
763+
defer rows.Close()
764+
765+
var name, setting, unit, shortDesc, vartype string
766+
for rows.Next() {
767+
err = rows.Scan(&name, &setting, &unit, &shortDesc, &vartype)
768+
if err != nil {
769+
return errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err))
770+
}
771+
772+
if vartype == "bool" {
773+
var val float64
774+
if setting == "on" {
775+
val = 1
799776
}
800777

801-
fval, ok := columnMapping.conversion(val)
802-
if !ok {
803-
nonFatalErrors = append(nonFatalErrors, errors.New(fmt.Sprintln("Unexpected error parsing column: ", namespace, columnName, val)))
804-
continue
778+
desc := newDesc(subsystem, name, shortDesc)
779+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val)
780+
continue
781+
}
782+
783+
val, err := strconv.ParseFloat(setting, 64)
784+
if err != nil {
785+
return errors.New(fmt.Sprintf("Error converting setting %q value %q to float: %s", name, setting, err))
786+
}
787+
788+
var suffix string
789+
// Units defined in: https://www.postgresql.org/docs/current/static/config-setting.html
790+
switch unit {
791+
case "":
792+
// Do nothing, no conversion required
793+
case "ms":
794+
suffix = "seconds"
795+
// -1 is special
796+
if val > 0 {
797+
val /= 1000
798+
}
799+
case "s":
800+
suffix = "seconds"
801+
case "min":
802+
suffix = "seconds"
803+
// -1 is special
804+
if val > 0 {
805+
val *= 60
806+
}
807+
case "h":
808+
suffix = "seconds"
809+
// -1 is special
810+
if val > 0 {
811+
val *= 60 * 60
812+
}
813+
case "d":
814+
suffix = "seconds"
815+
// -1 is special
816+
if val > 0 {
817+
val *= 60 * 60 * 24
818+
}
819+
case "kB":
820+
suffix = "bytes"
821+
// -1 is special
822+
if val > 0 {
823+
val *= math.Pow(2, 10)
805824
}
825+
case "MB":
826+
suffix = "bytes"
827+
// -1 is special
828+
if val > 0 {
829+
val *= math.Pow(2, 20)
830+
}
831+
case "GB":
832+
suffix = "bytes"
833+
// -1 is special
834+
if val > 0 {
835+
val *= math.Pow(2, 30)
836+
}
837+
case "TB":
838+
suffix = "bytes"
839+
// -1 is special
840+
if val > 0 {
841+
val *= math.Pow(2, 40)
842+
}
843+
case "8kB":
844+
suffix = "bytes"
845+
// -1 is special
846+
if val > 0 {
847+
val *= math.Pow(2, 13)
848+
}
849+
case "16MB":
850+
suffix = "bytes"
851+
// -1 is special
852+
if val > 0 {
853+
val *= math.Pow(2, 24)
854+
}
855+
default:
856+
return errors.New(fmt.Sprintf("Unknown unit %q for runtime variable %q", val, name))
857+
}
806858

807-
ch <- prometheus.MustNewConstMetric(columnMapping.desc, columnMapping.vtype, fval)
859+
if len(suffix) > 0 {
860+
name = fmt.Sprintf("%s_%s", name, suffix)
861+
shortDesc = fmt.Sprintf("%s [Converted to %s.]", shortDesc, suffix)
808862
}
863+
864+
desc := newDesc(subsystem, name, shortDesc)
865+
ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, val)
809866
}
810867

811-
return nonFatalErrors
868+
return nil
812869
}
813870

814871
// Query within a namespace mapping and emit metrics. Returns fatal errors if
@@ -941,11 +998,10 @@ func (e *Exporter) checkMapVersions(ch chan<- prometheus.Metric, db *sql.DB) err
941998
semanticVersion, err := parseVersion(versionString)
942999

9431000
// Check if semantic version changed and recalculate maps if needed.
944-
if semanticVersion.NE(e.lastMapVersion) || e.variableMap == nil || e.metricMap == nil {
1001+
if semanticVersion.NE(e.lastMapVersion) || e.metricMap == nil {
9451002
log.Infoln("Semantic Version Changed:", e.lastMapVersion.String(), "->", semanticVersion.String())
9461003
e.mappingMtx.Lock()
9471004

948-
e.variableMap = makeDescMap(semanticVersion, variableMaps)
9491005
e.metricMap = makeDescMap(semanticVersion, metricMaps)
9501006
e.queryOverrides = makeQueryOverrideMap(semanticVersion, queryOverrides)
9511007
e.lastMapVersion = semanticVersion
@@ -1010,9 +1066,8 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
10101066
// Lock the exporter maps
10111067
e.mappingMtx.RLock()
10121068
defer e.mappingMtx.RUnlock()
1013-
// Handle querying the show variables
1014-
nonFatalErrors := queryShowVariables(ch, db, e.variableMap)
1015-
if len(nonFatalErrors) > 0 {
1069+
if err := querySettings(ch, db); err != nil {
1070+
log.Infof("Error retrieving settings: %s", err)
10161071
e.error.Set(1)
10171072
}
10181073

postgres_exporter_integration_test.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"database/sql"
1515
"fmt"
16+
1617
_ "github.com/lib/pq"
1718
"github.com/prometheus/client_golang/prometheus"
1819
)
@@ -57,13 +58,10 @@ func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) {
5758
err = s.e.checkMapVersions(ch, db)
5859
c.Assert(err, IsNil)
5960

60-
// Check the show variables work
61-
nonFatalErrors := queryShowVariables(ch, db, s.e.variableMap)
62-
if !c.Check(len(nonFatalErrors), Equals, 0) {
63-
fmt.Println("## NONFATAL ERRORS FOUND")
64-
for _, err := range nonFatalErrors {
65-
fmt.Println(err)
66-
}
61+
err = querySettings(ch, db)
62+
if !c.Check(err, Equals, nil) {
63+
fmt.Println("## ERRORS FOUND")
64+
fmt.Println(err)
6765
}
6866

6967
// This should never happen in our test cases.

0 commit comments

Comments
 (0)