diff --git a/cmd/postgres_exporter/pg_breaking_changes.go b/cmd/postgres_exporter/pg_breaking_changes.go new file mode 100644 index 000000000..ddd4a76aa --- /dev/null +++ b/cmd/postgres_exporter/pg_breaking_changes.go @@ -0,0 +1,34 @@ +// Copyright 2021 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 main + +import ( + "github.com/blang/semver" +) + +type BreakingChanges struct { + Version string `yaml:"version"` + ver semver.Version + Columns map[string]string `yaml:"columns"` +} + +func (bc *BreakingChanges) parseVerTolerant() error { + bcVer, err := semver.ParseTolerant(bc.Version) + if err != nil { + return err + } + + bc.ver = bcVer + return nil +} diff --git a/cmd/postgres_exporter/postgres_exporter.go b/cmd/postgres_exporter/postgres_exporter.go index 36c524c8e..132e423a7 100644 --- a/cmd/postgres_exporter/postgres_exporter.go +++ b/cmd/postgres_exporter/postgres_exporter.go @@ -119,11 +119,12 @@ type Mapping map[string]MappingOptions // nolint: golint type UserQuery struct { - Query string `yaml:"query"` - Metrics []Mapping `yaml:"metrics"` - Master bool `yaml:"master"` // Querying only for master database - CacheSeconds uint64 `yaml:"cache_seconds"` // Number of seconds to cache the namespace result metrics for. - RunOnServer string `yaml:"runonserver"` // Querying to run on which server version + Query string `yaml:"query"` + Metrics []Mapping `yaml:"metrics"` + BreakingChanges []BreakingChanges `yaml:"breakingChanges"` + Master bool `yaml:"master"` // Querying only for master database + CacheSeconds uint64 `yaml:"cache_seconds"` // Number of seconds to cache the namespace result metrics for. + RunOnServer string `yaml:"runonserver"` // Querying to run on which server version } // nolint: golint @@ -517,7 +518,7 @@ func makeQueryOverrideMap(pgVersion semver.Version, queryOverrides map[string][] return resultMap } -func parseUserQueries(content []byte) (map[string]intermediateMetricMap, map[string]string, error) { +func parseUserQueries(content []byte, pgVersion semver.Version) (map[string]intermediateMetricMap, map[string]string, error) { var userQueries UserQueries err := yaml.Unmarshal(content, &userQueries) @@ -532,6 +533,30 @@ func parseUserQueries(content []byte) (map[string]intermediateMetricMap, map[str for metric, specs := range userQueries { level.Debug(logger).Log("msg", "New user metric namespace from YAML metric", "metric", metric, "cache_seconds", specs.CacheSeconds) newQueryOverrides[metric] = specs.Query + columnT := make(map[string]string) + for i := range specs.BreakingChanges { + if err := specs.BreakingChanges[i].parseVerTolerant(); err != nil { + return nil, nil, err + } + + if pgVersion.GE(specs.BreakingChanges[i].ver) { + for t := range specs.BreakingChanges[i].Columns { + columnT[t] = specs.BreakingChanges[i].Columns[t] + } + } + } + + // nolint: golint + // 2 because old - new + oldnew := make([]string, 0, 2*len(columnT)) + for t := range columnT { + oldnew = append(oldnew, t, columnT[t]) + } + + r := strings.NewReplacer(oldnew...) + + newQueryOverrides[metric] = r.Replace(newQueryOverrides[metric]) + metricMap, ok := metricMaps[metric] if !ok { // Namespace for metric not found - add it. @@ -571,7 +596,7 @@ func parseUserQueries(content []byte) (map[string]intermediateMetricMap, map[str // TODO: test code for all cu. // TODO: the YAML this supports is "non-standard" - we should move away from it. func addQueries(content []byte, pgVersion semver.Version, server *Server) error { - metricMaps, newQueryOverrides, err := parseUserQueries(content) + metricMaps, newQueryOverrides, err := parseUserQueries(content, pgVersion) if err != nil { return err } diff --git a/cmd/postgres_exporter/postgres_exporter_test.go b/cmd/postgres_exporter/postgres_exporter_test.go index 5747f0b07..5645a8ee8 100644 --- a/cmd/postgres_exporter/postgres_exporter_test.go +++ b/cmd/postgres_exporter/postgres_exporter_test.go @@ -410,7 +410,7 @@ func (s *FunctionalSuite) TestBooleanConversionToValueAndString(c *C) { func (s *FunctionalSuite) TestParseUserQueries(c *C) { userQueriesData, err := ioutil.ReadFile("./tests/user_queries_ok.yaml") if err == nil { - metricMaps, newQueryOverrides, err := parseUserQueries(userQueriesData) + metricMaps, newQueryOverrides, err := parseUserQueries(userQueriesData, semver.Version{Major: 13}) c.Assert(err, Equals, nil) c.Assert(metricMaps, NotNil) c.Assert(newQueryOverrides, NotNil) diff --git a/queries.yaml b/queries.yaml index 35b754319..f4f49e609 100644 --- a/queries.yaml +++ b/queries.yaml @@ -159,8 +159,24 @@ pg_database: description: "Disk space used by the database" pg_stat_statements: - query: "SELECT t2.rolname, t3.datname, queryid, calls, total_time / 1000 as total_time_seconds, min_time / 1000 as min_time_seconds, max_time / 1000 as max_time_seconds, mean_time / 1000 as mean_time_seconds, stddev_time / 1000 as stddev_time_seconds, rows, shared_blks_hit, shared_blks_read, shared_blks_dirtied, shared_blks_written, local_blks_hit, local_blks_read, local_blks_dirtied, local_blks_written, temp_blks_read, temp_blks_written, blk_read_time / 1000 as blk_read_time_seconds, blk_write_time / 1000 as blk_write_time_seconds FROM pg_stat_statements t1 JOIN pg_roles t2 ON (t1.userid=t2.oid) JOIN pg_database t3 ON (t1.dbid=t3.oid) WHERE t2.rolname != 'rdsadmin'" + query: "SELECT t2.rolname, t3.datname, queryid, calls, total_time_T / 1000 as total_time_seconds, min_time_T / 1000 as min_time_seconds, max_time_T / 1000 as max_time_seconds, mean_time_T / 1000 as mean_time_seconds, stddev_time_T / 1000 as stddev_time_seconds, rows, shared_blks_hit, shared_blks_read, shared_blks_dirtied, shared_blks_written, local_blks_hit, local_blks_read, local_blks_dirtied, local_blks_written, temp_blks_read, temp_blks_written, blk_read_time / 1000 as blk_read_time_seconds, blk_write_time / 1000 as blk_write_time_seconds FROM pg_stat_statements t1 JOIN pg_roles t2 ON (t1.userid=t2.oid) JOIN pg_database t3 ON (t1.dbid=t3.oid) WHERE t2.rolname != 'rdsadmin'" master: true + breakingChanges: + # should be in asc order by version + - version: '9.4.0' + columns: + total_time_T: total_time + min_time_T: min_time + max_time_T: max_time + mean_time_T: mean_time + stddev_time_T: stddev_time + - version: '13.0.0' + columns: + total_time_T: total_exec_time + min_time_T: min_exec_time + max_time_T: max_exec_time + mean_time_T: mean_exec_time + stddev_time_T: stddev_exec_time metrics: - rolname: usage: "LABEL"