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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ This will build the docker image as `prometheuscommunity/postgres_exporter:${bra
* `--collector.stat_statements.query_length`
Maximum length of the statement text. Default is 120.

* `--collector.stat_statements.limit`
Maximum number of statements to return. Default is 100.
Comment on lines +159 to +160
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by: forgot to update the README in #1205


* `--collector.stat_statements.exclude_databases`
Comma-separated list of database names to exclude from `pg_stat_statements` metrics. Default is none.

* `--collector.stat_statements.exclude_users`
Comma-separated list of user names to exclude from `pg_stat_statements` metrics. Default is none.

* `[no-]collector.stat_user_tables`
Enable the `stat_user_tables` collector (default: enabled).

Expand Down
77 changes: 72 additions & 5 deletions collector/pg_stat_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"database/sql"
"fmt"
"log/slog"
"strings"

"github.com/alecthomas/kingpin/v2"
"github.com/blang/semver/v4"
Expand All @@ -30,9 +31,11 @@ const (
)

var (
includeQueryFlag *bool = nil
statementLengthFlag *uint = nil
statementLimitFlag *uint = nil
includeQueryFlag *bool = nil
statementLengthFlag *uint = nil
statementLimitFlag *uint = nil
excludedDatabasesFlag *string = nil
excludedUsersFlag *string = nil
)

func init() {
Expand All @@ -56,21 +59,53 @@ func init() {
"Maximum number of statements to return.").
Default(defaultStatementLimit).
Uint()
excludedDatabasesFlag = kingpin.Flag(
fmt.Sprint(collectorFlagPrefix, statStatementsSubsystem, ".exclude_databases"),
"Comma-separated list of database names to exclude. (default: none)").
Default("").
String()
excludedUsersFlag = kingpin.Flag(
fmt.Sprint(collectorFlagPrefix, statStatementsSubsystem, ".exclude_users"),
"Comma-separated list of user names to exclude. (default: none)").
Default("").
String()
}

type PGStatStatementsCollector struct {
log *slog.Logger
includeQueryStatement bool
statementLength uint
statementLimit uint
excludedDatabases []string
excludedUsers []string
}

func NewPGStatStatementsCollector(config collectorConfig) (Collector, error) {
var excludedDatabases []string
if *excludedDatabasesFlag != "" {
for db := range strings.SplitSeq(*excludedDatabasesFlag, ",") {
if trimmed := strings.TrimSpace(db); trimmed != "" {
excludedDatabases = append(excludedDatabases, trimmed)
}
}
}

var excludedUsers []string
if *excludedUsersFlag != "" {
for user := range strings.SplitSeq(*excludedUsersFlag, ",") {
if trimmed := strings.TrimSpace(user); trimmed != "" {
excludedUsers = append(excludedUsers, trimmed)
}
}
}

return &PGStatStatementsCollector{
log: config.logger,
includeQueryStatement: *includeQueryFlag,
statementLength: *statementLengthFlag,
statementLimit: *statementLimitFlag,
excludedDatabases: excludedDatabases,
excludedUsers: excludedUsers,
}, nil
}

Expand Down Expand Up @@ -115,7 +150,9 @@ var (
)

const (
pgStatStatementQuerySelect = `LEFT(pg_stat_statements.query, %d) as query,`
pgStatStatementQuerySelect = `LEFT(pg_stat_statements.query, %d) as query,`
pgStatStatementExcludeDatabases = `AND pg_database.datname NOT IN (%s) `
pgStatStatementExcludeUsers = `AND pg_get_userbyid(userid) NOT IN (%s) `

pgStatStatementsQuery = `SELECT
pg_get_userbyid(userid) as user,
Expand All @@ -136,6 +173,7 @@ const (
WITHIN GROUP (ORDER BY total_time)
FROM pg_stat_statements
)
%s
ORDER BY seconds_total DESC
LIMIT %s;`

Expand All @@ -158,6 +196,7 @@ const (
WITHIN GROUP (ORDER BY total_exec_time)
FROM pg_stat_statements
)
%s
ORDER BY seconds_total DESC
LIMIT %s;`

Expand All @@ -180,6 +219,7 @@ const (
WITHIN GROUP (ORDER BY total_exec_time)
FROM pg_stat_statements
)
%s
ORDER BY seconds_total DESC
LIMIT %s;`
)
Expand All @@ -198,11 +238,13 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
if c.includeQueryStatement {
querySelect = fmt.Sprintf(pgStatStatementQuerySelect, c.statementLength)
}
databaseFilter := c.buildExcludedDatabasesClause()
userFilter := c.buildExcludedUsersClause()
statementLimit := defaultStatementLimit
if c.statementLimit > 0 {
statementLimit = fmt.Sprintf("%d", c.statementLimit)
}
query := fmt.Sprintf(queryTemplate, querySelect, statementLimit)
query := fmt.Sprintf(queryTemplate, querySelect, databaseFilter+userFilter, statementLimit)

db := instance.getDB()
rows, err := db.QueryContext(ctx, query)
Expand Down Expand Up @@ -319,3 +361,28 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
}
return nil
}

func (c PGStatStatementsCollector) buildExcludedDatabasesClause() string {
if len(c.excludedDatabases) == 0 {
return ""
}

databases := make([]string, 0, len(c.excludedDatabases))
for _, db := range c.excludedDatabases {
databases = append(databases, fmt.Sprintf("'%s'", strings.ReplaceAll(db, "'", "''")))
}

return fmt.Sprintf(pgStatStatementExcludeDatabases, strings.Join(databases, ", "))
}

func (c PGStatStatementsCollector) buildExcludedUsersClause() string {
if len(c.excludedUsers) == 0 {
return ""
}

users := make([]string, 0, len(c.excludedUsers))
for _, user := range c.excludedUsers {
users = append(users, fmt.Sprintf("'%s'", strings.ReplaceAll(user, "'", "''")))
}
return fmt.Sprintf(pgStatStatementExcludeUsers, strings.Join(users, ", "))
}
Loading
Loading