Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/postgres_exporter/postgres_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type Mapping map[string]MappingOptions

// Regex used to get the "short-version" from the postgres version field.
var versionRegex = regexp.MustCompile(`^\w+ ((\d+)(\.\d+)?(\.\d+)?)`)
var lowestSupportedVersion = semver.MustParse("9.1.0")
var lowestSupportedVersion = semver.MustParse("10.0.0")

// Parses the version of postgres into the short version string we can use to
// match behaviors.
Expand Down
2 changes: 1 addition & 1 deletion cmd/postgres_exporter/postgres_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ func (s *FunctionalSuite) TestBooleanConversionToValueAndString(c *C) {
func (s *FunctionalSuite) TestParseUserQueries(c *C) {
userQueriesData, err := os.ReadFile("./tests/user_queries_ok.yaml")
if err == nil {
metricMaps, newQueryOverrides, err := parseUserQueries(userQueriesData)
metricMaps, newQueryOverrides, err := parseUserQueries(userQueriesData, distributionStandard)
c.Assert(err, Equals, nil)
c.Assert(metricMaps, NotNil)
c.Assert(newQueryOverrides, NotNil)
Expand Down
38 changes: 32 additions & 6 deletions cmd/postgres_exporter/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@ import (
"gopkg.in/yaml.v2"
)

// UserQuery represents a user defined query
const (
distributionStandard = "standard"
distributionAurora = "aurora"
// '!' is a reserved character indicating that the query is not supported on Aurora and should be skipped for Aurora instances.
notSupportedByAurora = "!"
)

// UserQuery represents a user defined query, including support for Aurora, if needed
type UserQuery struct {
Query string `yaml:"query"`
Metrics []Mapping `yaml:"metrics"`
Query string `yaml:"query"` // Standard query
QueryAurora string `yaml:"query_aurora"` // Aurora specific query
Metrics []Mapping `yaml:"metrics"` // Metrics to be collected
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
Expand Down Expand Up @@ -197,7 +205,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, distribution string) (map[string]intermediateMetricMap, map[string]string, error) {
var userQueries UserQueries

err := yaml.Unmarshal(content, &userQueries)
Expand All @@ -211,7 +219,25 @@ 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

// Query selection logic:
// For Aurora: use query_aurora if defined and not empty, otherwise use query if defined and not empty.
// If query_aurora is set to '!', skip this query for Aurora (not supported).
// For standard (non-Aurora): always use query.
switch distribution {
case distributionAurora:
if specs.QueryAurora != "" {
if specs.QueryAurora == notSupportedByAurora {
continue
}
newQueryOverrides[metric] = specs.QueryAurora
} else {
newQueryOverrides[metric] = specs.Query
}
default:
newQueryOverrides[metric] = specs.Query
}

metricMap, ok := metricMaps[metric]
if !ok {
// Namespace for metric not found - add it.
Expand Down Expand Up @@ -251,7 +277,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, server.distribution)
if err != nil {
return err
}
Expand Down
104 changes: 104 additions & 0 deletions cmd/postgres_exporter/queries_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (C) 2023 Percona LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package main

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseUserQueries_DistributionSelection(t *testing.T) {
cases := []struct {
name string
yamlInput string
distribution string
wantQuery string
}{
{
name: "Standard uses query",
yamlInput: `
pg_replication:
query: "standard"
query_aurora: "aurora"
`,
distribution: "standard",
wantQuery: "standard",
},
{
name: "Aurora uses query_aurora",
yamlInput: `
pg_replication:
query: "standard"
query_aurora: "aurora"
`,
distribution: "aurora",
wantQuery: "aurora",
},
{
name: "Aurora falls back to query",
yamlInput: `
pg_replication:
query: "standard"
`,
distribution: "aurora",
wantQuery: "standard",
},
{
name: "Aurora skips if neither",
yamlInput: `
pg_replication:
`,
distribution: "aurora",
wantQuery: "",
},
{
name: "Standard query only",
yamlInput: `
pg_replication:
query: "standard"
`,
distribution: "standard",
wantQuery: "standard",
},
{
name: "Aurora query only",
yamlInput: `
pg_replication:
query_aurora: "aurora"
`,
distribution: "aurora",
wantQuery: "aurora",
},
{
name: "Not supported by Aurora",
yamlInput: `
pg_replication:
query: "standard"
query_aurora: "!"
`,
distribution: "aurora",
wantQuery: "",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, metricsQueries, err := parseUserQueries([]byte(tc.yamlInput), tc.distribution)
require.NoError(t, err)
require.Equal(t, tc.wantQuery, metricsQueries["pg_replication"])
})
}
}
19 changes: 14 additions & 5 deletions cmd/postgres_exporter/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import (
// Server describes a connection to Postgres.
// Also it contains metrics map and query overrides.
type Server struct {
db *sql.DB
labels prometheus.Labels
master bool
runonserver string
db *sql.DB
distribution string
labels prometheus.Labels
master bool
runonserver string

// Last version used to calculate metric map. If mismatch on scrape,
// then maps are recalculated.
Expand Down Expand Up @@ -80,7 +81,15 @@ func NewServer(dsn string, opts ...ServerOpt) (*Server, error) {
labels: prometheus.Labels{
serverLabelName: fingerprint,
},
metricCache: make(map[string]cachedMetrics),
metricCache: make(map[string]cachedMetrics),
distribution: distributionStandard,
}

// Detect Aurora by checking if aurora_version function exists
row := db.QueryRow("SELECT to_regproc('aurora_version') IS NOT NULL;")
var isAurora bool
if err := row.Scan(&isAurora); err == nil && isAurora {
s.distribution = distributionAurora
}

for _, opt := range opts {
Expand Down
91 changes: 0 additions & 91 deletions collector/pg_xlog_location.go

This file was deleted.

61 changes: 0 additions & 61 deletions collector/pg_xlog_location_test.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#### Queries are commented due to PMM-8859
pg_replication:
query: "SELECT CASE WHEN NOT pg_is_in_recovery() THEN 0 ELSE GREATEST (0, EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))) END AS lag"
# Standard query
- query: "SELECT CASE WHEN NOT pg_is_in_recovery() THEN 0 ELSE GREATEST (0, EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))) END AS lag"
# Aurora query
- query_aurora: "SELECT CASE WHEN pg_is_in_recovery() THEN (SELECT COALESCE(MAX(replica_lag_in_msec), 0) / 1000.0 FROM aurora_replica_status()) ELSE 0 END AS lag"
master: true
metrics:
- lag:
Expand Down
Loading