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
48 changes: 44 additions & 4 deletions collector/pg_stat_statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ func NewPGStatStatementsCollector(config collectorConfig) (Collector, error) {
}

var (
statStatementsPlansTotal = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "plans_total"),
"Number of times planned",
[]string{"user", "datname", "queryid"},
prometheus.Labels{},
)
statStatementsPlansSecondsTotal = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "plans_seconds_total"),
"Total time spent in planning the statement, in seconds",
[]string{"user", "datname", "queryid"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

queryID looks like a label with unbounded cardinality. Could we drop it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I need it, for example to get the ratio of calls by plan and find queries which are badly used (for example prepare + bind inside a loop)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't work well with Prometheus though 😅.

Maybe that could become an exemplar label in the histogram?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don’t understand the problem. It’s already the same in all the other metrics from pg_stat_statements.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Apologies, I probably should have said this from the beginning. I'm not a maintainer for this repository! If the maintainers are ok with it, that's what counts

@ArthurSens ArthurSens Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What I meant by "this doesn't work well with Prometheus" is that a single query will have a single ID and generate a single timeseries; another query will get a different ID and therefore become a brand-new timeseries.

All those unique IDs will become an entry in Prometheus's Posting index (good blog in case you'd like to read more). A gigantic posting index will make PromQL queries slower and increase resource usage

prometheus.Labels{},
)
statStatementsCallsTotal = prometheus.NewDesc(
prometheus.BuildFQName(namespace, statStatementsSubsystem, "calls_total"),
"Number of times executed",
Expand Down Expand Up @@ -159,6 +171,8 @@ const (
pg_database.datname,
pg_stat_statements.queryid,
%s
0 as plans_total,
0 as plans_seconds_total,
Comment on lines +174 to +175

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not emitting the metric at all sounds better than a 0, people might misinterpret this as the table actually returning 0.

pg_stat_statements.calls as calls_total,
pg_stat_statements.total_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
Expand All @@ -182,6 +196,8 @@ const (
pg_database.datname,
pg_stat_statements.queryid,
%s
pg_stat_statements.plans as plans_total,
pg_stat_statements.total_plan_time / 1000.0 as plans_seconds_total,
pg_stat_statements.calls as calls_total,
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
Expand All @@ -205,6 +221,8 @@ const (
pg_database.datname,
pg_stat_statements.queryid,
%s
pg_stat_statements.plans as plans_total,
pg_stat_statements.total_plan_time / 1000.0 as plans_seconds_total,
pg_stat_statements.calls as calls_total,
pg_stat_statements.total_exec_time / 1000.0 as seconds_total,
pg_stat_statements.rows as rows_total,
Expand Down Expand Up @@ -259,13 +277,13 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
defer rows.Close()
for rows.Next() {
var user, datname, queryid, statement sql.NullString
var callsTotal, rowsTotal sql.NullInt64
var secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal sql.NullFloat64
var plansTotal, callsTotal, rowsTotal sql.NullInt64
var plansSecondsTotal, secondsTotal, blockReadSecondsTotal, blockWriteSecondsTotal sql.NullFloat64
var columns []any
if c.includeQueryStatement {
columns = []any{&user, &datname, &queryid, &statement, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
columns = []any{&user, &datname, &queryid, &statement, &plansTotal, &plansSecondsTotal, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
} else {
columns = []any{&user, &datname, &queryid, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
columns = []any{&user, &datname, &queryid, &plansTotal, &plansSecondsTotal, &callsTotal, &secondsTotal, &rowsTotal, &blockReadSecondsTotal, &blockWriteSecondsTotal}
}
if err := rows.Scan(columns...); err != nil {
return err
Expand All @@ -292,6 +310,28 @@ func (c PGStatStatementsCollector) Update(ctx context.Context, instance *instanc
}
seen[key] = struct{}{}

plansTotalMetric := 0.0
if plansTotal.Valid {
plansTotalMetric = float64(plansTotal.Int64)
}
ch <- prometheus.MustNewConstMetric(
statStatementsPlansTotal,
prometheus.CounterValue,
plansTotalMetric,
userLabel, datnameLabel, queryidLabel,
)

plansSecondsTotalMetric := 0.0
if plansSecondsTotal.Valid {
plansSecondsTotalMetric = plansSecondsTotal.Float64
}
ch <- prometheus.MustNewConstMetric(
statStatementsPlansSecondsTotal,
prometheus.CounterValue,
plansSecondsTotalMetric,
userLabel, datnameLabel, queryidLabel,
)

callsTotalMetric := 0.0
if callsTotal.Valid {
callsTotalMetric = float64(callsTotal.Int64)
Expand Down
Loading